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);
注意:虽然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的更多相关文章
- module_init宏解析 linux驱动的入口函数module_init的加载和释放
linux驱动的入口函数module_init的加载和释放 http://blog.csdn.net/zhandoushi1982/article/details/4927579 void free_ ...
- [C#] 了解过入口函数 Main() 吗?带你用批处理玩转 Main 函数
了解过入口函数 Main() 吗?带你用批处理玩转 Main 函数 目录 简介 特点 方法的参数 方法的返回值 与批处理交互的一个示例 简介 我们知道,新建一个控制台应用程序的时候,IDE 会同时创建 ...
- 背景建模技术(三):背景减法库(BGS Library)的基本框架与入口函数main()的功能
背景减法库(BGS Library = background subtraction library)包含了37种背景建模算法,也是目前国际上关于背景建模技术研究最全也最权威的资料.本文将更加详细的介 ...
- linux驱动的入口函数module_init的加载和释放【转】
本文转载自:http://blog.csdn.net/zhandoushi1982/article/details/4927579 就像你写C程序需要包含C库的头文件那样,Linux内核编程也需要包含 ...
- 深入解析Linux中的fork函数
1.定义 #include <unistd.h> #include<sys/types.h> pid_t fork( void ); pid_t 是一个宏定义,其实质是int, ...
- 如何测试Linux 中的wait函数能不能等待子进程的子进程?
#include <stdio.h> #include <stdlib.h> int main() { pid_t pid = fork(); switch(pid) { : ...
- [fork]Linux中的fork函数详解
---------------------------------------------------------------------------------------------------- ...
- 关于linux中的system函数
Linux下使用system()函数一定要谨慎 https://blog.csdn.net/senen_wakk/article/details/51496322 system()正确应用 https ...
- linux中c语言编程main函数和参数
linux下main函数的的标准调用函数的标准形式 int main(int char,char *argv[]) 在main函数的两个参数中,argc必须是整型变量,其是命令行的参数的数目,argv ...
随机推荐
- vs13的内存占用 关闭之
.如何关闭CodeLens呢? 在VS菜单栏 >> 工具 >> 选项 >> 文本编辑器 >> 所有语言 >> CodeLens In VS ...
- MFC -- 遍历Dialog中的子元素(控件)
CWnd *win = GetWindow(GW_CHILD);//获取第一个子控件 while(win) { win代表子控件,可以通过win来获取子控件的信息,如下述两行代码 //iCtrlId ...
- linux内核学习之四 系统调用
一 概念区分 提到linux系统调用,不得不区分几个比较容易混淆的概念: 系统调用:系统调用就是一种特殊的接口.通过这个接口,用户可以访问内核空间.系统调用规定了用户进程进入内核的具体位置. 应用程 ...
- android studio集成融云 SDK 后在部分机型启动对话时崩溃
最初构建项目是 eclipse, 后来切换到 android studio来做开发. 后来多个用户反馈在android4.4机型上存在启动对话崩溃的问题.但是IOS 版工程从来没有重现. 调试报错信息 ...
- ibatis入门教程
转载自 http://www.cnblogs.com/ycxyyzw/archive/2012/10/13/2722567.html iBatis 简介: iBatis 是apache 的一个开源项 ...
- centos 6 安装 gitlib
安装gitlab-----------1. 下载 gitlabcurl -O https://downloads-packages.s3.amazonaws.com/centos-6.5/gitlab ...
- 2014年6月份第2周51Aspx源码发布详情
AMX高效自定义分页控件(WinForm)源码 2014-6-9 [VS2008]2014.6.9更新内容: 1. 更改用户自定义分页控件功能布局.大大精简了调用分页自定义控件的代码,和使用系统 ...
- 应用间共享数据方法(一)---sharepreferce
SharedPreferences类,它是一个轻量级的存储类,特别适合用于保存软件配置参数. SharedPreferences保存数据,其背后是用xml文件存放数据,文件存放在/data/data/ ...
- 捉虫记(四)线程安全导致的HighCpu
一个朋友QQ群里说网站启动后会cpu很高,想要帮忙看一下dump. 1.打开windbg加载dump文件后第一个命令lmf,这个命令显示加载的dll以及路径,这样子可以找个dll来帮忙加载sos,(额 ...
- Eclipse使用指定JDK,无需配置Path变量
修改Eclipse安装目录下的eclipse.ini配置文件 将下面内容添加到文件的首部 -vmF:/Lunatic/IDE/JDK/jdk1.7.0_67/jdk1.7.0_67/bin/javaw ...