c语言中的main函数讨论
**从刚开始写C程序,相比大家便开始写main()了。虽然无数的教科书和老师告诉我们main是程序的入口。那么main函数是怎么被调用的,怎么传入参数,返回的内容到哪里了,返回的内容是什么?接下来我们来探讨一下这个问题。**
main()函数的形式
早期教材这么写: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[])前者表明不需要函数的参数时,使用(void)形式,如果需要参数,还是使用(int argc, char *argv[]),但是实际使用时,我也会使用(int argc, char *argv)这种形式,与不同平台和编译器有关而以,在Unix-like平台上,也可以int main(int argc, char *argv[], char *envp[]),后面需要再讨论。
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。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 0;
main();
return 0;
}
不过要小心调用方式,和退出条件,避免无穷递归。
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()等方式来得到环境变量。
c语言中的main函数讨论的更多相关文章
- C语言中的main函数以及main函数是如何被调用的
main函数是C语言中比较特殊的函数,C程序总是从main函数开始执行,main函数的原型是: int main(int argc, char *argv[]); 其中argc是命令行参数的个数,ar ...
- C语言中的main函数的参数解析
main()函数既可以是无参函数,也可以是有参的函数.对于有参的形式来说,就需要向其传递参数.但是其它任何函数均不能调用main()函数.当然也同样无法向main()函数传递,只能由程序之外传递而来. ...
- c语言中的rewind函数,Win CE 不支持,可用fseek函数替换
FILE *read = fopen(cXmlFile,"rb"); if (read) { fseek(read, 0L, SEEK_END); int len = ftell( ...
- c语言中的malloc函数
少壮不努力,大一的时候c语言学得不扎实,最近学数据结构的时候看到c语言中malloc函数都不知道了,这里记录一下,避免以后再忘. malloc的全称是memory allocation,中文叫动态内存 ...
- C语言中的system函数参数及其作用
函数名: system 功 能: 发出一个DOS命令 用 法: int system(char *command); system函数已经被收录在标准c库中,可以直接调用 system() ...
- linux下C语言中的flock函数用法 【转】
表头文件 #include<sys/file.h> 定义函数 int flock(int fd,int operation); 函数说明 flock()会依参数operation所指 ...
- C语言中的memset函数和数组指针
代码: #include <iostream> #include <cstring> using namespace std; int main(){ ] = {}; mems ...
- C语言中的system函数參数具体解释
http://blog.csdn.net/pipisorry/article/details/33024727 函数名: system 功 能: 发出一个DOS命令 用 法: int sy ...
- linux下C语言中的flock函数使用方法 .
表头文件 #include<sys/file.h> 定义函数 int flock(int fd,int operation); 函数说明 flock()会依參数operation所指 ...
随机推荐
- JZOJ 5197. 【NOIP2017提高组模拟7.3】C
5197. [NOIP2017提高组模拟7.3]C Time Limits: 1000 ms Memory Limits: 262144 KB Detailed Limits Goto Pro ...
- VirtualBox下vim无法正常使用问题解决
由原来的使用VMware转到使用Virtual Box,发现其vim编辑器不是特别好用,需要进行一下更改设置: 1.使用命令删除vim,sudo apt-get remove vim-common 2 ...
- kubectl alias auto complete
平时kubectl命令管理kubernetes,敲久了就觉得比较麻烦,想着使用alias k来代替kubectl,可是当输入k时没有了自动补全的功能 这里在 ~/.bashrc 添加如下配置后,可以自 ...
- uva1422 二分法+优先队列贪心
题意:有n个任务,每个任务必须在在时刻[r, d]之内执行w的工作量(三个变量都是整数).处理器执行的速度可以变化,当速度为s时,一个工作量为w的任务需要 执行的时间为w/s个单位时间.另外不一定要连 ...
- Median of Two Sorted Arrays LeetCode Java
两排序好的数组,找中位数 描述There are two sorted arrays A and B of size m and n respectively. Find the median of ...
- 笔记-python-__new__()
笔记-python-__new__() 1. __new__() __new__() 是在新式类中新出现的方法,它作用在构造方法建造实例之前. 验证代码: class Person(obj ...
- laravel5.2总结--blade模板
## 1.基本用法 ``` ##情形1 $name = laravel5 <div class="title"> {{$name}} {{$name}}</div ...
- AppDOMain(摘录)
AppDomain是CLR的运行单元,它可以加载Assembly.创建对象以及执行程序. AppDomain是CLR实现 代码隔离 的基本机制. 每一个AppDomain可以单独运行.停止:每个App ...
- java中常用的几种缓存类型介绍
在平时的开发中会经常用到缓存,比如locache.redis等,但一直没有对缓存有过比较全面的总结.下面从什么是缓存.为什么使用缓存.缓存的分类以及对每种缓存的使用分别进行分析,从而对缓存有更深入的了 ...
- PHP 与 Redis 入门教程
Redis 官方推荐的 PHP 客户端是 Predis 和 phpredis. 前者是完全使用 PHP 代码实现的原生客户端,而后者则是使用 C 语言编写的 PHP 扩展.在功能上两者区别并不大,就性 ...