C中的main函数是个另类(一)

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?待查。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值