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);

注意:虽然main函数是一个特殊的函数,是程序运行的入口,但它毕竟也是一个函数,是可以被调用的。如:

 int   main()
{
if(...)
return ;
main();
return ;
}

不过要小心调用方式,和退出条件,避免无穷递归。

4. shell中执行程序
    通过前几次和上面的分析,我们终于基本弄清了应用程序的执行过程,再回顾一遍: 在某个交互式shell中敲入./test, 此shell fork()/clone()出一个子进程,这个子进程执行
    
    execve("./test",char * const argv[], char * const envp[])
    
    execve加载./test,并把参数argv[],envp[]一步一步传递下去。加载了./test之后,从./test的入口开始执行,即ELF文件中的_start(),_start()调用__libc_start_main(),最后到了main。
    
    int main(int argc, char *argv[], char *envp[])
    
    看着这个main的定义和execve相似吧,没错main中的参数都是execve一步步传递下来的。argc是命令行参数个数,argv[]存储着各个参数的指针(注意argv[0]通常是程序名,argv[1]开始才是命令行参数。这是由shell设置的),envp[]存储着环境变量表。然而在标准C中只定义了int main(int argc, char *argv[]),所以unix-like平台也提供了全局变量environ指向环境变量表。
    extern char **environ;
    当然也可以用getenv和putenv来访问特定的环境变量。

对了,父shell还在wait()./test的结束呢,不错,test中main函数return的值,在被__libc_start_main() exit之后,终于被父shell抓住了,可以用$?访问。
    如$> ./test
      $> echo $?
    可以得到test返回的值。这样,你就知道main()函数中return的意义,以及如何在shell中使用了吧。尽管可以return任何值,也建议用return 0来表示程序正常结束。这样别人用shell脚本调用你写的程序的时候,就可以$?等于0来判断你的程序是否正常执行了。

最后小结一下:
1. 避免使用void main(),尽量使用int main() 或者 int main(int argc, char *argv[])。
2. 在main的结尾记得 return int;, 最好用return 0;表示程序的正常结束。
3. main函数和普通函数一样也是能被调用的。
4. main return的值最终会返回给其调用者,如shell中执行的程序,可以在shell中用$?得到其返回值。
5. 在unix-like环境中,可以使用int main(int argc, char *argv[], char *envp[]), extern char **environ; , getenv()等方式来得到环境变量。

Linux中的入口函数main的更多相关文章

  1. module_init宏解析 linux驱动的入口函数module_init的加载和释放

    linux驱动的入口函数module_init的加载和释放 http://blog.csdn.net/zhandoushi1982/article/details/4927579 void free_ ...

  2. [C#] 了解过入口函数 Main() 吗?带你用批处理玩转 Main 函数

    了解过入口函数 Main() 吗?带你用批处理玩转 Main 函数 目录 简介 特点 方法的参数 方法的返回值 与批处理交互的一个示例 简介 我们知道,新建一个控制台应用程序的时候,IDE 会同时创建 ...

  3. 背景建模技术(三):背景减法库(BGS Library)的基本框架与入口函数main()的功能

    背景减法库(BGS Library = background subtraction library)包含了37种背景建模算法,也是目前国际上关于背景建模技术研究最全也最权威的资料.本文将更加详细的介 ...

  4. linux驱动的入口函数module_init的加载和释放【转】

    本文转载自:http://blog.csdn.net/zhandoushi1982/article/details/4927579 就像你写C程序需要包含C库的头文件那样,Linux内核编程也需要包含 ...

  5. 深入解析Linux中的fork函数

    1.定义 #include <unistd.h> #include<sys/types.h> pid_t fork( void ); pid_t 是一个宏定义,其实质是int, ...

  6. 如何测试Linux 中的wait函数能不能等待子进程的子进程?

    #include <stdio.h> #include <stdlib.h> int main() { pid_t pid = fork(); switch(pid) { : ...

  7. [fork]Linux中的fork函数详解

    ---------------------------------------------------------------------------------------------------- ...

  8. 关于linux中的system函数

    Linux下使用system()函数一定要谨慎 https://blog.csdn.net/senen_wakk/article/details/51496322 system()正确应用 https ...

  9. linux中c语言编程main函数和参数

    linux下main函数的的标准调用函数的标准形式 int main(int char,char *argv[]) 在main函数的两个参数中,argc必须是整型变量,其是命令行的参数的数目,argv ...

随机推荐

  1. 写一个程序可以对两个字符串进行测试,得知第一个字符串是否包含在第二个字符串中。如字符串”PEN”包含在字符串“INDEPENDENT”中。

    package lovo.test; import java.util.Scanner; public class Java { @param args public static void main ...

  2. Python 3.x 使用csv模块写入数据

    with open(fileName,'w',newline='') as f: self.fileNames = ['timestamp','elapsedtime'] writer = csv.D ...

  3. JS 4 新特性:混合属性(mixins)

    Ext JS4的新特征1:混合属性(mixins) 组合是Extjs4的新特性,可用于实现多继承的情况.该属性会以同步方式加载类文件,并实例化该类(译者推理其内部使用Ext.create方法).直接上 ...

  4. 这种代码怎么改写?以致于在下次增加CustomsType时,不需要再加 if 语句。

    最近看到项目里一段代码如下: excelObject excel = new excelObject(); if (loadbill.CustomsType == 1) excel.IDownload ...

  5. 常用js字符串方法学习总结

    2016-06-15 js数组和字符串方法有很多,并且有一部分在使用的过程中有很多方法是很容易被混淆的,今天来总结一下js中数组和字符串的方法. ♦数组(Array)的方法 1.push() 和 po ...

  6. android.os.NetworkOnMainThreadException异常

    在android4.0以前,访问网络的代码可以写在UI主线程,但是在android4.0以上就不能在ui主线程中访问网络了,会出现android.os.NetworkOnMainThreadExcep ...

  7. 5-26课堂作业——组员投票Alpha版存在的问题

    我们在课上讨论了Alpha版目前发现的问题,并通过投票的方式,选出其中三个认为是当前须紧要解决的问题. 问题: 1.缺少数据库: 2.用户体验不良好,游戏难度分区不明显: 3.团队成员共同工作时间少.

  8. NLog的使用

    1.项目添加NLog的项目引用: 2.将工程的framework框架改为3.5或4.0,将配置文件[NLog.config]拷贝至运行的bin/(release/debug)路径下 3.项目中代码使用 ...

  9. C++中的private/protected/public

    访问权限: 继承关系:

  10. NOIP2016 模拟赛

    7.10 T1:求出一个矩阵中平均数大于0的子矩阵的最大面积. T2:给出一个N行的,第I行有n+1-i的倒三角形,从中选取m个数,只有当前数的左上角和右上角都被选是才能选当前数,求选的数字的最大和 ...