标签:存在 命名 打开 无符号 函数 接受 png struct 内存
当内核使用一个exec函数执行C程序时,在调用main函数之前先调用一个特殊的启动例程,可执行程序将此例程指定为程序的起始地址。启动例程从内核获取命令行参数和环境变量,然后为调用main函数做好准备。
我们常用gcc main.c -o main
命令编译一个程序,其实也可以分三步做,第一步生成汇编代码,第二步生成目标文件,第三步生成可执行文件:
1 $ gcc -S main.c 2 $ gcc -c main.s 3 $ gcc main.o
-S
选项生成汇编代码, -c
选项生成目标文件,此外 -E
选项只做预处理而不编译,如果不加这些选项则 gcc
执行完整的编译步骤,直到最后链接生成可执行文件为止。gcc命令的选项图这些选项都可以和 -o
搭配使用,给输出的文件重新命名而不使用 gcc
默认的文件名( xxx.c
、 xxx.s
、 xxx.o
和 a.out
),例如 gcc main.o -o main
将 main.o
链接成可执行文件 main
。
gcc
做链接,gcc
其实是调用ld
将目标文件crt1.o
和我们的hello.o
链接在一起。crt1.o
里面已经提供了_start
入口点,我们的汇编程序中再实现一个_start
就是多重定义了,链接器不知道该用哪个,只好报错。另外,crt1.o
提供的_start
需要调用main
函数,而我们的汇编程序中没有实现main
函数,所以报错。gcc
做链接就没错了,整个程序的入口点是crt1.o
中提供的_start
,它首先做一些初始化工作(以下称为启动例程,Startup Routin),然后调用C代码中提供的main
函数。所以,以前我们说main
函数是程序的入口点其实不准确,_start
才是真正的入口点,而main
函数是被_start
调用的。main
函数最标准的原型应该是int main(int argc, char *argv[])
,也就是说启动例程会传两个参数给main
函数,这两个参数的含义我们学了指针以后再解释。我们到目前为止都把main
函数的原型写成int main(void)
,这也是C标准允许的,如果你认真分析了上一节的习题,你就应该知道,多传了参数而不用是没有问题的,少传了参数却用了则会出问题。main
函数是被启动例程调用的,所以从 main
函数 return
时仍返回到启动例程中, main
函数的返回值被启动例程得到,如果将启动例程表示成等价的C代码(实际上启动例程一般是直接用汇编写的),则它调用 main
函数的形式是:
1 exit(main(argc, argv));
也就是说,启动例程得到 main
函数的返回值后,会立刻用它做参数调用 exit
函数。 exit
也是 libc
中的函数,它首先做一些清理工作,然后调用上一章讲过的 _exit
系统调用终止进程, main
函数的返回值最终被传给 _exit
系统调用,成为进程的退出状态。我们也可以在 main
函数中直接调用 exit
函数终止进程而不返回到启动例程,例如:
1 #include <stdlib.h> 2 3 int main(void) 4 { 5 exit(4); 6 }
这样和 int main(void) { return 4; }
的效果是一样的。在Shell中运行这个程序并查看它的退出状态:
1 ./a.out 2 echo $? 3 4
按照惯例,退出状态为0表示程序执行成功,退出状态非0表示出错。注意,退出状态只有8位,而且被Shell解释成无符号数,如果将上面的代码改为 exit(-1);
或 return -1;
,则运行结果为
./a.out echo $? 255
注意,如果声明一个函数的返回值类型是 int
,函数中每个分支控制流程必须写 return
语句指定返回值,如果缺了 return
则返回值不确定(想想这是为什么),编译器通常是会报警告的,但如果某个分支控制流程调用了 exit
或 _exit
而不写 return
,编译器是允许的,因为它都没有机会返回了,指不指定返回值也就无所谓了。使用 exit
函数需要包含头文件 stdlib.h
,而使用 _exit
函数需要包含头文件 unistd.h
。
进程终止的方式有8种,前5种为正常终止,后三种为异常终止:
1 从main函数返回;
2 调用exit函数;
3 调用_exit或_Exit;
4 最后一个线程从启动例程返回;
5 最后一个线程调用pthread_exit;
6 调用abort函数;
7 接到一个信号并终止;
8 最后一个线程对取消请求做出响应。
1 #include <stdlib.h> 2 void exit( int status ); 3 void _Exit( int status ); 4 #include <unistd.h> 5 void _exit( int status );
这三个函数用于正常终止一个程序, _exit和_Exit立即进入内核,而exit则要先做一些清理工作(调用执行各终止处理程序,关闭所有标准I/O流),再进入内核。三个函数所带的整型参数称为终止状态或退出状态,如果(a)调用这些函数不带参数,(b) main函数中的return语句无返回值,(c) main函数没有声明返回类型为整型,则进程的终止状态是未定义的。 main函数返回一个整型值与用该值调用exit是等价的。
1 #include<stdlib.h> 2 #include<conio.h> 3 #include<stdio.h> 4 int main(int argc,char*argv[]) 5 { 6 int status; 7 printf("Enter either 1 or 2\n"); 8 status=getch(); 9 /*Sets DOS error level*/ 10 exit(status-‘0‘); 11 /*Note:this line is never reached*/ 12 return 0; 13 }
1 #include<stdio.h> 2 #include<stdlib.h> 3 void func1(void) 4 { 5 printf("in func1\n"); 6 } 7 void func2(void) 8 { 9 printf("in func2\n"); 10 } 11 void func3(void) 12 { 13 printf("in func3\n"); 14 } 15 int main() 16 { 17 atexit(func3); 18 atexit(func2); 19 atexit(func1);
20 sleep(5); 20 printf("In main\n"); 21 exit(0); 22 }
过程分析:atexit()函数先注册三个func()函数,然后等待5秒,再打印“int main”(如果main()函数输出部分后面没有“\n”,则main()函数要输出的内容会先放到标准输出缓冲区中,当main()中调用exit()函数的时候,会做一些自身清理工作,同时刷新缓冲区的内容),当执行到exit(0)时,exit()会自动调用这些已注册的函数,但是由于压栈的过程中先入后出的原则,所以先注册的函数最后执行。
一个进程可以登记多达32个函数,这些函数将由exit自动调用,通常这32个函数被称为终止处理程序,并调用atexit函数来登记这些函数,atexit的参数是一个函数地址,当调用此函数时无须传递任何参数,该函数也不能返回值,atexit函数称为终止处理程序注册程序,注册完成以后,当函数终止是exit()函数会主动的调用前面注册的各个函数,但是exit函数调用这些函数的顺序于这些函数登记的顺序是相反的,我认为这实质上是参数压栈造成的,参数由于压栈顺序而先入后出。同时如果一个函数被多次登记,那么该函数也将多次的执行。exit函数运行时首先会执行由atexit()函数登记的函数,然后会做一些自身的清理工作,同时刷新所有输出流、关闭所有打开的流并且关闭通过标准I/O函数tmpfile()创建的临时文件。
exit()函数用于在程序运行的过程中随时结束程序,exit的参数state是返回给操作系统,返回0表示程序正常结束,非0表示程序非正常结束。
标签:存在 命名 打开 无符号 函数 接受 png struct 内存
原文地址:http://www.cnblogs.com/33debug/p/6770458.html