In-depth analysis of the main function of the C language!

return value of main

The return value of the main function is used to describe the exit status of the program. If it returns 0, the program exits normally. The meaning of other numbers returned is determined by the system. Usually, a non-zero return means the program exited abnormally.

void main()

Some books use void main( ), but this is wrong. Void main() has never been defined in C/C++.

Bjarne Stroustrup, the father of C++, clearly stated in the FAQ on his homepage "The definition void main() {/*… */} is not and never has been C++, nor has it even been C." This may be Because in C and C++, the prototype of a function that does not receive any parameters and does not return any information is "void foo(void);".

Probably because of this, many people mistakenly think that the main function can be defined as void main(void) if the return value of the program is not required. However this is wrong! The return value of the main function should be defined as an int type, which is stipulated in the C and C++ standards.

Although in some compilers, void main() can be compiled, not all compilers support void main(), because void main has never been defined in the standard.

In g++3.2, if the return value of the main function is not of type int, it will not pass compilation at all. And gcc3.2 will issue a warning. Therefore, in order to have a good portability of the program, int main () must be used. The test is as follows:

#include <stdio.h>

void main()
{
    printf("Hello world\n");
    return;
}

Operation result: g++ test.c

main()

Since the main function has only one return value type, can it be omitted? Regulation: If the return value is not clearly marked, the default return value is int, which means that main() is equivalent to int main(), not to void main().

In C99, the standard requires the compiler to give at least a warning for the usage of main(), but in C89 this kind of writing is allowed. But for the standardization and readability of the program, the type of return value should be clearly pointed out. Test code:

#include <stdio.h>

main()
{
    printf("Hello world\n");
    return 0;
}

operation result:

C and C++ standards

In the C99 standard, only the following two definitions are correct:

int main( void ) 
int main( int argc, char *argv[] ) 

If you don’t need to get parameters from the command line, use int main(void); otherwise, use int main( int argc, char *argv[] ). Of course, there are other ways to pass parameters, which will be discussed separately in the next section.

The return value type of the main function must be int, so that the return value can be passed to the caller of the program (such as the operating system), which is equivalent to exit(0) to determine the execution result of the function.

The following two main functions are defined in C++89:

int main( ) 
int main( int argc, char *argv[] ) 

int main() is equivalent to int main( void) in C99; the usage of int main( int argc, char*argv[]) is also the same as that defined in C99. Similarly, the return value type of the main function must also be int.

return statement

If the return statement is not written at the end of the main function, both C99 and C++89 stipulate that the compiler should automatically add return 0 to the generated object file, indicating that the program exits normally.

However, it is recommended that you add a return statement at the end of the main function. Although this is not necessary, it is a good habit. Under Linux, we can use the shell command: echo $? to view the return value of the function.

#include <stdio.h>

int main()
{
    printf("Hello world\n");
}

operation result:

At the same time, it should be noted that the return value of return will undergo type conversion, for example: if return 1.2; it will be coerced to 1, that is, the true return value is 1, the same thing, return'a'; if it is true, return The value is 97,; but if return "abc"; a warning will be reported because implicit type conversion cannot be performed.

Test the meaning of the return value of the main function

As mentioned earlier, if the main function returns 0, it means that the program exits normally. Usually, a non-zero return means the program exited abnormally. At the end of this article, test it: test.c:

#include <stdio.h>

int main()
{
    printf("c 语言\n");
    return 11.1; 
}

Perform the following in the terminal:

➜  testSigpipe git:(master) ✗ vim test.c
➜  testSigpipe git:(master) ✗ gcc test.c
➜  testSigpipe git:(master) ✗ ./a.out && echo "hello world"  #&&与运算,前面为真,才会执行后边的
c 语言

It can be seen that the operating system considers the main function to fail because the return value of the main function is 11.

➜  testSigpipe git:(master) ✗ ./a.out 
➜  testSigpipe git:(master) ✗ echo $?
11

If the return value in the main function should be 0:

➜  testSigpipe git:(master) ✗ vim test.c
➜  testSigpipe git:(master) ✗ gcc test.c 
➜  testSigpipe git:(master) ✗ ./a.out && echo "hello world" #hello
c 语言
hello world

It can be seen that, as we expect, the main function returns 0, which means that the function exits normally and the execution is successful; if it returns non-zero, it means that the function is abnormal and the execution fails.

main function passing parameters

The first thing to note is that some people may think that the main function cannot be passed in parameters, but in fact this is wrong. The main function can obtain parameters from the command line, thereby improving code reusability.

Function prototype

When passing parameters for the main function, the optional main function prototype is:

int main(int argc , char* argv[],char* envp[]);

Parameter Description:

①. The first parameter argc represents the number of incoming parameters.

②. The second parameter char* argv[] is a string array, which is used to store the pointer array to the string parameter. Each element points to a parameter. The meaning of each member is as follows:

argv[0]: point to the full path name of the program.

argv[1]: points to the first string after the name of the executing program, which represents the first parameter actually passed in.

argv[2]: points to the second string after the name of the executing program, which represents the second parameter passed in.

…… argv[n]: Point to the nth character string after the name of the executing program, representing the nth parameter passed in.

Regulation: argv[argc] is NULL, which means the end of the parameter.

③. The third parameter char* envp[] is also a string array, mainly to save the variable string in the user environment, ending with NULL. Each element of envp[] contains a string in the form of ENVVAR=value, where ENVVAR is an environment variable, and value is its corresponding value.

Once envp is passed in, it is just a simple string array and will not change with the dynamic settings of the program. You can use the putenv function to modify environment variables in real time, and you can also use getenv to view environment variables in real time, but envp itself will not change; it is usually used less.

Note : The parameters char* argv[] and char* envp[] of the main function represent string arrays, and the writing form is more than char* argv[], the corresponding argv[][] and char** argv can be .

char* envp[]

Write a small test program to test the third parameter of the main function:

#include <stdio.h>

int main(int argc ,char* argv[] ,char* envp[])
{
    int i = 0;

    while(envp[i++])
    {
        printf("%s\n", envp[i]);
    }

    return 0;
}

Running result: some screenshots

The information obtained by envp[] is equivalent to the result of the env command under Linux.

Common version

When using the parameterized version of the main function, the most commonly used is: **int main(int argc, char* argv[]); **The variable names argc and argv are conventional names, of course, they can be replaced with other names .

The form of command line execution is: executable file name parameter 1 parameter 2…… parameter n . Use spaces to separate the executable file name, parameters, and parameters.

Sample program

#include <stdio.h>

int main(int argc, char* argv[])
{

    int i;
    printf("Total %d arguments\n",argc);

    for(i = 0; i < argc; i++)
    {
        printf("\nArgument argv[%d]  = %s \n",i, argv[i]);
    }

    return 0;
}

operation result:

➜  cpp_workspace git:(master) ✗ vim testmain.c 
➜  cpp_workspace git:(master) ✗ gcc testmain.c 
➜  cpp_workspace git:(master) ✗ ./a.out 1 2 3    #./a.out为程序名 1为第一个参数 , 2 为第二个参数, 3 为第三个参数
Total 4 arguments
Argument argv[0]  = ./a.out 
Argument argv[1]  = 1 
Argument argv[2]  = 2 
Argument argv[3]  = 3 
Argument argv[4]  = (null)    #默认argv[argc]为null

The execution order of main

Some people may say that, in other words, the main function must be the first function executed by the program. So, is this really the case? I believe that after reading this section, there will be a different understanding.

Why it is said that main() is the entrance of the program

The entry point of the program under the linux system is "_start", this function is part of the linux system library (Glibc), when our program and Glibc are linked together to form the final executable file, this function is the entry function for program execution initialization . To illustrate through a test program:

#include <stdio.h>

int main()
{
    printf("Hello world\n");
    return 0;
}

Compile:

gcc testmain.c -nostdlib     # -nostdlib (不链接标准库)

Program execution will cause an error: /usr/bin/ld: warning: cannot find entry symbol _start; This symbol is not found

so:

  1. The compiler defaults to find the __start symbol, not main

  2. __start This symbol is the start of the program

  3. main is a symbol called by the standard library

So, what is the relationship between this _start and the main function? Let's explore further below.

The realization of the _start function The entry is specified by the default link script of the ld linker, of course, the user can also set it through parameters. _start is implemented by assembly code. It is roughly represented by the following pseudo code:

void _start()
{
  %ebp = 0;
  int argc = pop from stack
  char ** argv = top of stack;
  __libc_start_main(main, argc, argv, __libc_csu_init, __linc_csu_fini,
  edx, top of stack);
}

The corresponding assembly code is as follows:

_start:
 xor ebp, ebp //清空ebp
 pop esi //保存argc,esi = argc
 mov esp, ecx //保存argv, ecx = argv

 push esp //参数7保存当前栈顶
 push edx //参数6
 push __libc_csu_fini//参数5
 push __libc_csu_init//参数4
 push ecx //参数3
 push esi //参数2
 push main//参数1
 call _libc_start_main

hlt

It can be seen that before calling _start, the loader will push the user's parameters and environment variables onto the stack.

Work before the main function runs

It can be seen from the implementation of _start that a series of work needs to be done before the main function is executed. The main thing is to initialize system related resources:

Some of the stuff that has to happen before main():

set up initial stack pointer 

initialize static and global data 

zero out uninitialized data 

run global constructors

Some of this comes with the runtime library's crt0.o file or its __start() function. Some of it you need to do yourself.

Crt0 is a synonym for the C runtime library.

1. Set the stack pointer

2. Initialize static static and global global variables, that is, the content of the data section

3. Assign initial values ​​to the uninitialized part: numeric values ​​of short, int, long, etc. are 0, bool is FALSE, pointer is NULL, etc., that is, the content of the .bss section

4. Run the global constructor, similar to the global constructor in C++

5. Pass the parameters of the main function, argc, argv, etc. to the main function, and then actually run the main function

The code that runs before main

Below, let's talk about what code will run before the mian function is executed: (1) The constructor of the global object will be executed before the main function.

(2) The space allocation and initial value assignment of some global variables, objects and static variables, objects is before the execution of the main function, and after the main function is executed, some operations such as releasing space and releasing resource usage rights must be performed.

(3) After the process is started, some initialization codes (such as setting environment variables, etc.) must be executed, and then jump to main for execution. The construction of the global object is also before main.

(4) Through the keyword attribute , let a function run before the main function, perform some data initialization, module loading verification, etc.

Sample code

①, through the keyword attribute

#include <stdio.h>

__attribute__((constructor)) void before_main_to_run() 
{ 
    printf("Hi~,i am called before the main function!\n");
    printf("%s\n",__FUNCTION__); 
} 

__attribute__((destructor)) void after_main_to_run() 
{ 
    printf("%s\n",__FUNCTION__); 
    printf("Hi~,i am called after the main function!\n");
} 

int main( int argc, char ** argv ) 
{ 
    printf("i am main function, and i can get my name(%s) by this way.\n",__FUNCTION__); 
    return 0; 
}

② Initialization of global variables

#include <iostream>

using namespace std;

inline int startup_1()
{
    cout<<"startup_1 run"<<endl;
    return 0;
}

int static no_use_variable_startup_1 = startup_1();

int main(int argc, const char * argv[]) 
{
    cout<<"this is main"<<endl;
    return 0;
}

At this point, we have finished talking about the things before the main function is executed. So, do you think that the main function is also the last function of the program?

The result is of course not. After the main function runs, there are other functions that can be executed. After the main function is executed, it returns to the entry function, and the entry function performs cleanup work, including global variable destruction, heap destruction, shutdown I/O, etc., and then proceed The system call ends the process.

Function executed after main function

1. The destructor of the global object will be executed after the main function; 2. The function registered with atexit will also be executed after the main.

atexit function

Prototype:

int atexit(void (*func)(void)); 

The atexit function can "register" a function so that this function will be called when the main function terminates normally. When the program terminates abnormally, the functions registered through it will not be called.

The compiler must allow the programmer to register at least 32 functions. If the registration is successful, atexit returns 0, otherwise it returns a non-zero value, and there is no way to cancel the registration of a function.

Before any standard cleanup operations performed by exit, the registered functions are called sequentially in the reverse order of the registration order. Each called function does not accept any parameters, and the return type is void. The registered function should not try to refer to any object whose storage class is auto or register (for example, by pointer), unless it is defined by itself.

Registering the same function multiple times will cause this function to be called multiple times. The final operation of the function call is the pop process. main() is also a function. At the end, the atexit function is called in the order of popping out of the stack. Therefore, the function atexit is the same as the function being stacked and popped. It is first-in-last-out and registered first. After execution. The callback cleanup function can be registered through atexit. You can add some cleanup work to these functions, such as memory release, closing open files, closing socket descriptors, releasing locks, and so on.

#include<stdio.h>
#include<stdlib.h>

void fn0( void ), fn1( void ), fn2( void ), fn3( void ), fn4( void );

int main( void )

{
  //注意使用atexit注册的函数的执行顺序:先注册的后执行
    atexit( fn0 );  
    atexit( fn1 );  
    atexit( fn2 );  
    atexit( fn3 );  
    atexit( fn4 );

    printf( "This is executed first.\n" );
    printf("main will quit now!\n");

    return 0;

}

void fn0()
{
    printf( "first register ,last call\n" );
}

void fn1(
{
    printf( "next.\n" );
}

void fn2()
{
    printf( "executed " );
}

void fn3()
{
    printf( "is " );
}

void fn4()
{
    printf( "This " );
}

Author: z_ryan

Original: https://blog.csdn.net/z_ryan/category_7316855.html


1. Preview of the exciting content of the GD32 Arm MCU Internet of Things developer online course!

2. Yang Fuyu Column|Looking for corners that can be overtaken: The great man said that he was in the middle

3. RISC-V is actually against the trend!

4. Don't ignore it! Fatal loopholes in embedded code!

5. Extra! LoRa long-distance communication "resident" MCU since then

6. Is technology really neutral?

Disclaimer: This article is reproduced online, and the copyright belongs to the original author. If you are involved in copyright issues, please contact us, we will confirm the copyright based on the copyright certification materials you provide and pay the author's remuneration or delete the content.

Guess you like

Origin blog.csdn.net/DP29syM41zyGndVF/article/details/112690324