为什么写C程序在结束时不需要系统调用exit?
一、系统调用exit
众所周知,一个程序的执行和结束,都是需要发起系统调用。
调用execve
系统调用,让操作系统加载该程序,调用exit
系统调用,表示程序要退出,让操作系统清理空间。
写过汇编语言的都知道,一般都会使用下面的语句来(以Intel汇编为例)发起系统调用exit:
mov eax,1
int 0x80
这是发起系统调用exit
,用于程序退出。
但是,在C语言中,却没有这样系统调用退出的代码。
?你说这个东西太偏了,没用?
接着看下面的代码。
二、gcc链接和ld链接
有一段下面的代码:
// main.c
int main(void)
{
return 0;
}
1. gcc编译链接
使用gcc编译链接,然后运行是没有任何问题的:
gcc -c main.c -o main.o -m32 # 编译
gcc main.o -o main -m32 # 链接
./main # 执行
我这里使用参数-m32表示生成32位的程序,是因为32位的简单,容易分析。
这里我把编译和链接分开写的,先用-c参数,编译,生成.o目标文件;然后生成可执行文件
运行如下图,没有任何问题:
但是如果在链接时使用ld呢?
2. gcc编译,ld连接
gcc在链接时还是使用的ld,但是对ld进行了封装。
同样上面的代码main.c
,使用gcc编译,ld链接,然后执行:
gcc -c main.c -o main.o -m32 # 编译
ld -m elf_i386 main.o -o main -e main # 链接
./main
使用ld链接时,使用-e表示程序的入口,这里使用指名main函数为程序入口
运行如下图:
此时出现了,大名鼎鼎的 segmentation fault (core dumped)。
为什么会出现这样的错误呢?
3. 为什么会报错呢?
一般segmentation fault (core dumped)都是非法访问了没有权限访问的内存空间,所以dump了。
那么我的程序这么简单,就一个return 0,怎么会非法访问呢?
// main.c
int main(void)
{
return 0;
}
就是在于,这个程序没有退出,没有执行系统调用exit,导致执行到其他地方的代码去了,然后非法访问。
3.1 如果手动添加系统调用exit
下面我自己使用内联汇编,手动添加一个系统调用exit退出:
void sys_exit(void)
{
asm(\
"mov $1, %eax;\
int $0x80;"
);
}
int main(void)
{
sys_exit();
}
然后同样的使用gcc编译,使用ld链接,执行:
上图可以看出,程序完美执行,不会segmentation fault了。
三、为什么使用gcc编译链接C程序不用添加系统调用exit?
上面的程序可以看到,C程序使用gcc编译,使用ld链接,因为没有系统调用exit,所以出错,这才符合我们的想法:程序退出时要调用exit系统调用。
但是,我们在写C程序时,直接用gcc编译链接,运行并没有问题,为什么呢?
就是因为gcc在链接时,做了点封装,让用户看不到exit系统调用的执行。
简单地讲,使用gcc链接C程序时,入口默认是_start函数,然后_start会调用main(这个应该多多少少听过)。
_start在执行时,会做下面的事情:
- 初始化环境
- 调用main函数
- 调用系统调用exit结束运行
这就解决了下面的问题:
- 为什么C程序不需要系统调用exit来结束运行
- 是因为使用gcc链接时,默认的程序入口是_start函数。
- _start函数会调用main函数,main函数结束后,由**_start函数来调用系统调用exit**结束,
- 所以对于用户来说,不用在C程序代码里编写系统调用exit。