Linux上程序执行的入口--Main

main()函数,想必大家都不陌生了,从刚开始写程序的时候,大家便开始写main(),我们都知道main是程序的入口。那main作为一个函数,又是谁调用的它,它是怎么被调用的,返回给谁,返回的又是什么?这次我们来探讨一下这个问题。

1. main()函数的形式
先来说说main函数的定义,较早开始写C程序的肯定都用过这样的定义void main(){},其实翻翻C/C++标准,从来没有定义过void main()。
在C标准中main的定义只有两种:
        int main(void)
        int main(int argc, char *argv[])
        在C++标准中main的定义也只有两种:
        int main( )
        int main(int argc, char *argv[])
  
    换句话说:当你的程序不需要命令行参数的时候用int main(), 当需要命令行参数的时候请使用int main(int argc, char *argv[])
  
    不过标准归标准,在不同的平台上,不同的编译器中对main()的定义方式总有自己的实现,比如早期编译器对void main()的支持(现在gcc也支持,不过会给出一个warning)。特别的,因为历史的原因,在Unix-like平台上,大多还支持
        int main(int argc, char *argv[], char *envp[])
    其使用方式我们稍后再谈。

2. main()函数的返回   
    int main(...) 意味着需要return一个int值,如果不写,有的编译器会自动帮你添加一个return 0;,而有的则会返回一个随机值。为了避免不必要的问题,建议写的时候还是加上一个return 0;,浪费不了你多少时间,不是吗?
    所以一个完整的test.c文件应该为:
    int main(int argc, char *argv[])
    {
        return 0;
    }
    当然我们也可以尝试着让main返回一个long, double甚至是struct,更改main函数中的形参定义。这在有些编译器上是能编译通过的,不过可能会有一些警告(如GCC)。但是运行的时候如果编译器能做转换的还好,如返回long,float. 如果不能的话(如返回struct,或者main(int argc, char *argv0,char *argv1,char *argv2))会造成segmentation fault。
  
  
3. main()的调用和返回
    在了解了main()函数的定义和返回形式后,我们再来看看main函数是怎么被调用的,它又"return"给了谁。在"gcc的编译过程"一中,我们回顾了程序从源码到可执行程序的过程,在"应用程序在linux上是如何被执行的"一文中,我们回顾了可执行文件怎么被操作系统加载的,今天我们继续这个过程。
上文提到不管是在load_elf_binary()中或者使用了动态链接库,最后都执行到了应用程序的入口。不过这个入口不是main.而是_start()。
执行
    gcc -o test test.c
    readelf -a test
    可以看到test文件的Entry point address是0x80482e0,在往后看,这个地址是.text的地址(代码段的开始),也是_start()的地址。在_start()中又会调用__libc_start_main(),主要做一些程序的初始化工作,感兴趣的同学可以读读glibc中的源码,注释很清楚。然后主角登场了,在__libc_start_main()中最后会调用
    int result = main (argc, argv, __environ MAIN_AUXVEC_PARAM);//这是Unix-like下main函数的调用方式,这下大家明白main函数中形参的由来了吧。
    result中放着main函数的返回值,然后带着这个值退出。
    exit (result);

相关推荐