,大家都知道,我是做上层应用的,对底层不是很了解,更别说那帮人在讨论内核的时候,根本插不上话。更多的时候,还是默默记笔记,紧跟大佬们的步伐。
,于是,为了调研这个问题,也查了相关资料。今天借助本文,来分析下C语言中main()的实现,顺便解答下群里的这个问题。
,作为C/C++开发人员,都知道main()函数是一个可执行程序的入口函数,大都会像如下这样写:,但是,作为一个开发老油条,也仅仅知道是这样做的,当看到二哥提出这个问题的时候,第一反应是重载,但是大家都知道C语言是不支持重载的,那么有没有可能使用的是默认参数呢?如下这种:
,好了,为了验证我的疑问,咱们着手开始进行分析。
,ps:在cppreference上对于main()的声明有第三个参数即char *envp[],该参数是环境变量相关,因为我们使用更多的是不涉及此参数的方式,所以该参数不在本文的讨论范围内。
,为了能够更清晰的理解main()函数的执行过程,写了一个简单的代码,通过gdb查看堆栈信息,代码如下:,编译之后,我们通过gdb进行调试,在main()函数处设置断点,然后看堆栈信息,如下:,从上述gdb信息,我们看出main()位于栈顶,显然,我们的目的是分析main()的调用堆栈信息,而这种main()在栈顶的方式显然不足以解答我的疑问。
,于是,查阅了相关资料后,发现可以通过其它方式打印出更详细的堆栈信息。
,编译命令如下:
,然后gdb的相关命令(具体的命令可以网上查阅,此处不做过多分析):,然后在main()处设置断点,运行,查看堆栈信息,如下:,通过如上堆栈信息,我们看到_start()-->__libc_start_main()-->main(),看来应该在这俩函数中,开始分析~~
,为了查看_start()的详细信息,继续在_start()函数处打上断点,然后分析查看:,通过如上分析,没有看到_start()函数的可执行代码,于是通过网上搜索,发现_start()是用汇编编写,于是下载了glibc2.5源码,在路径处sysdeps/i386/elf/start.S,上述实现也是比较简单的:
,xorl %ebp, %ebp:将ebp寄存器清零。
,popl %esi、movl %esp, %ecx:装载器把用户的参数和环境变量压栈,实际上按照压栈的方法,栈顶的元素就是argc,接着其下就是argv和环境变量的数组。这两句相当于int argc = pop from stack; char **argv = top of stack。
,call BP_SYM (__libc_start_main):相当于调用__libc_start_main,调用的时候传入参数,包括argc、argv。
,上述逻辑功能,伪代码实现如下:,在上一节中,我们了解到,_start()才是整个可执行程序的入口函数,在_start()函数中调用__libc_start_main()函数,该函数声明如下:,可以看出,在该函数中,最终调用了main()函数,并传入了相关命令行。(result = main (argc, argv, __environ MAIN_AUXVEC_PARAM);)
,截止到此,我们了解了整个main()函数的调用过程,但是,仍然没有回答二哥的问题,main()是如何实现有参和无参两种方式的,其实说白了,在标准中,main()只有一种声明方式,即有参方式。无论是否有命令行参数,都调用该函数。如果有参数,则通过压栈出栈(对于x86 32位)或者寄存器(x86 64位)的方式获取参数,然后传入main(),如果命令行为空,则对应的字段为空(即没有从栈上取得对应的数据)。
© 版权声明
文章版权归作者所有,未经允许请勿转载。