C语言为什么必须有main函数

本文深入探讨了C程序为何必须包含main函数,通过分析程序执行流程和编译链接过程揭示了main函数作为程序入口点的重要性,并介绍了如何利用GCC特性实现特定函数在main前后执行。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

  1. #include <stdio.h>  
  2.   
  3. int main()  
  4. {  
  5.             printf("Hello World!";  
  6.             return 0;  
  7. }  
-------------------------------------------------helloworld.c-----------------------------------------------------

 
          本文的主要内容是:为什么必须有main
            一般的C语言教材会说,因为main是程序的入口点。仅此而已。到底什么是入口点,为什么需要入口点,将是本文讨论的主要内容。
            程序的入口点,顾名思义,是在程序执行的时候进入的地方,那我们就从C程序的执行过程入手,分析这个问题。hello_world.c经过预处理、编译、链接形成可执行文件(windows下的hello_world.exe)。注意,在链接阶段,除了我们自己写的代码与库函数之外,编译器还会链接一系列以crt开头的文件。
            在gcc-4.6.3下,输入gcc -o helloworld -v hello_world.c之后的链接内容截图如下


涉及到的crt系列文件包括 crt1.o,crti.o,crtn.o,crtbegin.o,crtend.o。这一系列crt.o的作用马上就会看到。           

当我们运行这个C程序的时候,我们需要一个可供程序运行的c runtimeenvironment。就好比操作系统需要运行在硬件之上,C程序需要运行在c runtimeenvironment之中。ubuntu下的c runtime enviroment就是由上述crt系列定义的(crt是cruntime的缩写)。
crt在main程序运行之前为我们做了这些:
1.建立stdin/stdout/stderr流
2.将main函数接受的两个参数(argc,argv)压入栈中,供main调用
3.不同的操作系统还可能要求的一些其他操作。
抽象介绍不好理解,上代码。
一个最简单的crt文件可以用如下汇编表示:


  
[plain] view plain copy
  1. extern main  
  2. extern exit  
  3. global _start  
  4.      _start:  
  5.      mov eax, [esp + 4]   
  6.      mov ebx, [esp + 8]  
  7.      push ebx    
  8.      push eax  
  9.      call main  
  10.      add esp, 8   
  11.      push eax  
  12.      call exit  
上面这段代码执行了一个crt文件的最基本操作:读取需要传送给_start的两个参数argcargv,然后以逆序压入栈中,然后调用main函数,main返回时,从栈中弹出argcargv。然后将main的返回值存储在eax中。调用exit函数告诉系统main已结束。
如果在链接时不加入crt部分会有什么效果?还是让gcc告诉我们吧。-nostdlib命令会让gcc在链接时不链接一切标准函数库,包括crt系列。(为了方便测试,我将hello_world中的printf函数注释掉了,然后将文件重命名为test_main.c。不注释掉printf的效果还是各位看官自己测试吧)
输入gcc -o test test_main.c -nostdlib 然后运行test 效果如下图所示:


根据warning与error的内容我们可以很容易的推断出 main函数没有被加载到的结论。
关于“为什么必须有main”的问题暂时讨论至此,很多具体到细节的内容仍待完善。

main函数补充:一般情况下我们用的main函数都是int main()。但是标准C实际上定义了四种main的原型:
int main(void) 
int main()
int main(int argc, char **argv)
int main(int argc, char *argv[])

一二类似,三四类似。第三(四)种的直接作用就是我们可以从终端中读取参数。
argc=0时,argv[0]为程序本身的名字。代码如下
#include <stdio.h>

int main(int argc,char **argv){
  int i;
  for(i=0;i<argc;i++)
    printf("argc[%d] : %s\n",i,argv[i]);
  return 0;
}

运行结果如下:


在linux环境下还存在第五种声明:
int main(int argc, char **argv, char **envp)
envp代表了程序的运行时环境。具体内容还请各位看官自行写代码研究。


藩外篇:我的函数可以在main之前执行!
(下述内容来源于 http://www.drdobbs.com/cpp/184401956
在标准c下,函数是不可能在main之前执行的。但是有了gcc这个强大的编译器,nothing is impossible。
在gcc环境下,我们可以通过给函数增加一个attribute的方式,实现在main开始之前或者结束之后执行函数。格式如下:
void myConstructor( void ) __attribute__ ((constructor));

void myDestructor( void ) __attribute__ ((destructor));



经测试,下述在gcc 4.6.4下运行正常。
[plain] view plain copy
  1. void myStartupFun (void)__attribute__((constructor));  
  2.    
  3. void myCleanupFun (void)__attribute__((destructor));  
  4.    
  5. void myStartupFun (void)  
  6. {  
  7.     printf("startupcode before main()\n");  
  8. }  
  9.    
  10. voidmyCleanupFun (void)  
  11. {  
  12.     printf("cleanupcode after main()\n");  
  13. }  
  14.    
  15. int main (void)  
  16. {  
  17.     printf("hello\n");  
  18.     return0;  
  19. }  


输出结果如下图所示:



末尾附加C语言小知识点一个与大家分享:
  1. #include <stdio.h>  
  2. int main()  
  3. {  
  4.         char *p = "helloworld";  
  5.         priintf("%c",*(ptr++));  
  6.         return 0;  
  7. }  
请问输出是什么?
下一篇预告:hello world是如何显示到屏幕上的--linux下C语言的标准输入输出流

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值