还差一点,全程序猴子的第一节课就要学习hello世界的程序了。 先来看看经典的c语言hello世界吧
/* hello.c */
#包含
int main () )
{
打印(hello world!
();
返回0;
}
这是一个再简单不过的程序。 但是,程序中有最重要的部分。 那就是我们还差一点就能在所有代码中看到的main函数。 我们编译成可执行文件,看符号表,过滤里面的函数。 例如,如下所示。 为了方便起见,我手动调整了grep的输出格式,所以和你的输出格式不同。
$ gcc hello.c -o hello
$ readelf -s hello | grep FUNC
num : valuesizetypebindvisndxname
2:000000000040040 c0funclocaldefault 13 call _ gmon _ start
3:00000000400430 funclocaldefault 13 _ _ do _ global _ dtors _ aux
3:0000000000004004 a0funclocaldefault 13 frame _ dummy
4033600000000000400580 funclocaldefault 13 _ _ do _ global _ ctors _ aux
4:00000000004004 e 02 funcglobaldefault 13 _ _ libc _ CSU _ fini
4:000000000004003 E0 funcglobaldefault 13 _ start
5:00000000000000000 funcglobaldefaultundputs @ @ glibc _ 2.2.5
5:0000000000004005 b 80 funcglobaldefault 14 _ fini
5:000000000000000000 funcglobaldefaultund _ _ libc _ start _ main @ @ glibc _ @
5:00000000004004 f 0137 funcglobaldefault 13 _ _ libc _ CSU _ init
62336000000000004004 c 421 funcglobaldefault 13 main
6336000000000000400390 funcglobaldefault 11 _ init
众所周知,用户的代码是从main函数执行的。 我们只写了一个main函数,从上面的函数表中可以看出有很多函数,比如_start函数。 实际上,程序的真正入口不是main函数。 使用以下命令编译hello.c代码
$ gcc hello.c -nostdlib
/usr/bin/LD : warning : cannotfindentrysymbol _ start; defaulting to 0000000000400144
-nostdlib命令是指如果未链接到标准库,并且找不到entry symbol _start,则会报告错误。 在这里,意味着找不到入口符号_start。 也就是说,程序的真正入口是_start函数
实际上,main函数只是用户代码的入口,由系统库调用。 在main函数之前,系统库会执行初始化任务,例如分配全局变量的内存。 初始化堆、线程等,执行main函数,然后使用exit () )函数进行清理。 用户可以自己实现_start函数
/* hello_start.c */
#包含
#包含
_start(void )
{
打印(hello world!
();
退出(0;
}
例如,执行以下编译命令
$ gcc hello _ start.c-no start files-o hello _ start
$ ./hello_start
hello世界!
这里的-nostartfiles的功能是donotusethestandardsystemstartupfileswhenlinking,也就是不使用标准的startup files,但是因为还是链接了系统库,所以程序让我们看看同样的符号表
$ readelf -s hello_start | grep FUNC
num : valuesizetypebindvisndxname
2033600000000000040035024 funcglobaldefault 10 _ start
2:00000000000000000 funcglobaldefaultundputs @ @ glibc _ 2.2.5
2:00000000000000000 funcglobaldefaultundexit @ @ glibc _ 2.2.5
现在只剩下三个函数了。 然后我们自己实现了。 因为其中printf只有一个参数由编译器优化为puts函数。 可以通过在编译时添加-fno-builtin选项来关闭优化
假设从_start函数中删除了exit(0)语句。 程序运行时出现core。 这是因为_start函数执行程序后就结束了。 在我们自己实现的_start中,没有调用exit )来清理内存
不,easy去除了主函数。 此时,您发现需要_start函数。 非常麻烦吗? 其实_start函数只是默认的入口,我们可以指定入口
/* hello_nomain.c */
#包含
#包含
int nomain () )
{
打印(hello world!
();
退出(0;
}
例如,用以下命令编译
$ gcc hello _ no main.c-no start files-enom ain-o hello _ no main
中-e选项允许指定程序条目符号。 请看符号表,例如以下所示
$ readelf-shello _ no main|grep func
num : valuesizetypebindvisndxname
20336000000000000000000 funcglobaldefaultundputs @ @ glibc _ 2.2.5
2:00000000000000000 funcglobaldefaultundexit @ @ glibc _ 2.2.5
2:0000000000040035024 funcglobaldefault 10 no main
通过对照hello_start的符号表,我们发现它只是用nomain替换了_start
至此,您已经清楚了,程序的缺省条目是标准库的_start函数。 进行调用用户的main函数的初始化工作。 最后做一些清理工作,我们可以自己编写_start函数来覆盖标准库的_start。 甚至可以自己指定程序的入口