C中的main函数是个另类。
ISO C99标准中对main函数的要求主要有两处:5.1.2.2.1 Program startup和5.1.2.2.3 Program termination。我们来看看Program startup一节对main是如何规定的。
1. The function called at program startup is named main
OK。 不过上一篇A Minimal Application in Assembly:不要迷信洋人 中提到需要指定程序执行入口点,而main函数不是执行入口点。哪_start从何而来?稍后我们有说明。
2. The implementation declares no prototype for this function.
也就是说,标准头文件没有main函数申明的。
3. It shall be defined with a return type of int and with no parameters:
int main(void) { /* ... */ }
没问题,书上是这么教的。
4.with two parameters (referred to here as argc and argv, though any names may be used, as they are local to the function in which they are declared):
int main(int argc, char *argv[]) { /* ... */ }
没问题,书上是这么教的。
5. or equivalent; or in some other implementation-defined manner.
这是一个后门。
在Linux下,
int main(int argc, char **argv, char **envp)
而Apple下,
int main(int argc, char **argv, char **envp, char **apple)
简单讨论一下
1. main函数是一个另类,因为1)没有地方有其申明,2)接受的参数可有可无,看上去象函数重载、但又不可能是重载(因为C不支持)
2. _start是在glibc中定义
glibc-2.10.1/sysdeps/i386/elf/start.S
.globl _start
.type _start,@function
_start:
所以应用程序中不需要也不能再有_start定义(除非你不和glibc link)。有人说_start是一个procedure,应该是不完全对。准确地说,_start代表两层含义:
1) 程序执行起始点
2) 不会自己返回,所以我们必须在最后调用exit,否则跑过头,程序出错被kernel杀掉。而procudure应该是要返回的。所以说_start不是procedure。
3. glibc中_start主要工作
2.1 把main的三个参数argc, argv, envp全部压栈
2.2 调用__libc_start_main,传入main作为第一个参数(函数指针)
2.3 __libc_start_main内先调用main,然后调用exit
result = main (argc, argv, __environ MAIN_AUXVEC_PARAM);
exit (result);
4. glibc总是把三个参数argc, argv, envp全部压栈。按照x86上stack frame的如下布局
12(%ebp) argv 8(%ebp) argc 0(%ebp) return address 在main()情况下,通过汇编也能取到argc, argv和envp(当然了,谁吃饱了要这样做?我们只不过用来说明问题)。代码例子如下。 $ cat arg.c #include int main() { int argc; char** argv; char** envp; asm volatile ("mov 8(%%ebp), %0" :"=r"(argc) ); printf("argc=%d/n", argc); asm volatile ("mov 12(%%ebp), %0" :"=r"(argv) ); while (*argv) { printf("%s/n", *argv); ++argv; } asm volatile ("mov 16(%%ebp), %0" :"=r"(envp) ); while (*envp) { printf("%s/n", *envp); ++envp; } return 0; } $ gcc -Wall arg.c -o arg $ ./arg argc=1 ./arg ORBIT_SOCKETDIR=/tmp/orbit-joshua SSH_AGENT_PID=1504 GPG_AGENT_INFO=/tmp/seahorse-yRQpOB/S.gpg-agent:1527:1 TERM=xterm SHELL=/bin/bash XDG_SESSION_COOKIE=13e2b301ee9d92afbf28a1074a3fa812-1270301380.469094-2041096598 GTK_RC_FILES=/etc/gtk/gtkrc:/home/joshua/.gtkrc-1.2-gnome2 WINDOWID=69206019 GTK_MODULES=canberra-gtk-module ...
总结:
有关main函数,C99标准没有讨论细节部分。我们这里给予点补充。对Linux的C应用程序开发人员来说,就当作有一个如下申明就行了:
int main(int argc, char *argv[], char *envp[]);
如果想钻研,可以去看glibc代码。
Note: 突然间,想起英文中的变量或者函数declaration,中文翻译为申明或者声 明,到底哪一个更好、还是都OK?待查。