从Entry Point到main函数调用(1)

本文详细解析了C程序从启动到执行main函数的过程,包括获取Win32平台版本、创建私有堆、初始化低级IO、获取命令行缓冲区与环境变量、设置参数与环境、初始化C数据以及调用main函数的完整流程。

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

 

转自 http://driftcloudy.iteye.com/blog/1048750

之前在调试exe时感觉很奇怪,为什么Entry Point并非直接进入到main函数。

举例来说,如果将一段空的C代码build为exe:

C代码 复制代码  收藏代码
  1. void main(){ }  

编译环境为:VC6 release。

再将该exe文件进行反汇编,那么从EP开始的代码部分大概形如:

ASM代码
push ebp
……
一段汇编代码
……
call Main函数
……
另一段汇编代码
……
retn

 

也就是说, 在执行一个exe文件时,总是要先运行一些指令,才能够开始调用Main函数。同样,当main函数执行完毕后,还需要运行一些指令完成收尾。为了弄清楚main函数调用前这些代码以及main函数执行后的代码,需要从CRT(C RunTime ,C的运行时库)开始研究。

 

(2)

Visual Studio自带了CRT的源码,VC6中CRT位于“VC98\CRT\SRC”目录。CRT 中的 crt0.c 文件规定了一整套C程序固定的执行流程。在 crt0.c 开头的注释部分有如下描述:

This the actual startup routine for apps. It calls the user's main routine [w]main() or [w]WinMain after performing C Run-Time Library initialization.

大概意思是,当C Run-Time Library 完成了初始化工作之后,才开始执行用户自定义的main 函数、WinMain 函数。

 

crt0.c 为了规定C程序执行的流程,定义了函数mainCRTStartupWinMainCRTStartup ,这两个函数也被称作启动函数 。它们的作用在函数注释中已经写的很清楚:

This routine does the C runtime initialization, calls main(), and then exits.

 

mainCRTStartup函数大概形如:

C代码 复制代码  收藏代码
  1. void mainCRTStartup(void){   
  2.     int mainret;   
  3.     ……   
  4.     __try {   
  5.         ……   
  6.         mainret = main(__argc, __argv, _environ); //在这里调用用户写的main函数    
  7.         exit(mainret);   
  8.     }   
  9.     __except ( _XcptFilter(GetExceptionCode(), GetExceptionInformation()) )   
  10.     {   
  11.         _exit( GetExceptionCode() );   
  12.     }   
  13. }  

当Windows系统执行一个C程序时,真正首先执行的是(win)mainCRTStartup函数。mainCRTStartup首先进行了一系列准备工作,例如heap的初始化、IO的初始化、获得命令行参数等等。当所有的准备工作都完成之后,再去调用用户自定义的main函数。最后,执行exit函数退出程序。因此对于exe,(win)mainCRTStartup函数才是真正的Entry point。

 

另外,crt0.c 中还有相似的函数:wWinMainCRTStartup、wmainCRTStartup,它们是Unicode版本程序的EP,这里可以暂时不用去管。windows为了照顾Unicode程序,很多API都提供了两种版本,一种是针对ANSI字符,还有一种是针对Unicode字符。

 

这四个函数是放在一起定义的,crt0.c 中的源码如下:

Cpp代码 复制代码  收藏代码
  1. #ifdef _WINMAIN_                   /* _WINMAIN_被定义时,表示GUI程序 */   
  2.   
  3. #ifdef WPRFLAG                     /* WPRFLAG被定义时,表示Unicode字符 */   
  4. void wWinMainCRTStartup(   
  5. #else    
  6. void WinMainCRTStartup(   
  7. #endif   
  8.   
  9. #else                              /* 下面为CUI程序 */   
  10.   
  11. #ifdef WPRFLAG   
  12. void wmainCRTStartup(   
  13. #else    
  14. void mainCRTStartup(   
  15. #endif    
  16.   
  17. #endif   
  18.   
  19. void){   
  20. ……   
  21. }  
#ifdef _WINMAIN_                   /* _WINMAIN_被定义时,表示GUI程序 */

#ifdef WPRFLAG                     /* WPRFLAG被定义时,表示Unicode字符 */
void wWinMainCRTStartup(
#else 
void WinMainCRTStartup(
#endif

#else                              /* 下面为CUI程序 */

#ifdef WPRFLAG
void wmainCRTStartup(
#else 
void mainCRTStartup(
#endif 

#endif

void){
……
}

可以根据上面的源代码总结如下:

mainCRTStartup Console apps ANSI
wmainCRTStartup Console apps Unicode
WinMainCRTStartup Windows apps ANSI
wWinMainCRTStartup Windows apps Unicode

 

(3)

来具体看一下(win)mainCRTStartup函数。下面将mainCRTStartup函数的主要语句摘录了出来,这里去除了一些条件编译的代码,忽略了windows apps(_WINMAIN_)、Unicode版本的程序(WPRFLAG)、多线程(_MT),仅仅分析Console apps。

C代码 复制代码  收藏代码
  1. int mainret;   
  2.   
  3. // 获取Win32的版本   
  4. _osver = GetVersion();   
  5. _winminor = (_osver >> 8) & 0x00FF ;   
  6. _winmajor = _osver & 0x00FF ;   
  7. _winver = (_winmajor << 8) + _winminor;   
  8. _osver = (_osver >> 16) & 0x00FFFF ;   
  9.   
  10. // 创建了一个属于该进程的私有堆   
  11. if ( !_heap_init(0) )   
  12.     fast_error_exit(_RT_HEAPINIT);   
  13.   
  14.   
  15. __try {   
  16.     // 初始化低级IO   
  17.     _ioinit();   
  18.   
  19.     // 获取命令行缓冲区指针   
  20.     _acmdln = (char *)GetCommandLineA();   
  21.   
  22.     // 获取环境变量指针   
  23.     _aenvptr = (char *)__crtGetEnvironmentStringsA();   
  24.        
  25.     // 设置argv参数   
  26.     _setargv();   
  27.   
  28.     // 设置环境变量   
  29.     _setenvp();   
  30.   
  31.     // 初始化C数据   
  32.     _cinit();   
  33.   
  34.     __initenv = _environ;   
  35.   
  36.     // 调用main函数   
  37.     mainret = main(__argc, __argv, _environ);   
  38.     exit(mainret);   
  39. }   
  40. __except ( _XcptFilter(GetExceptionCode(), GetExceptionInformation()) )   
  41. {   
  42.     _exit( GetExceptionCode() );   
  43. }  

 

从这段代码可以大体窥视出 C 程序的启动流程:

1.获取WIN32平台的版本

2.调用_heap_init函数创建一个私有堆

3.初始化低级IO
4.获取命令行缓冲区、环境变量的指针

5.设置命令行参数与环境变量

6.初始化C数据

7.调用main函数

8.将main函数的调用结果传入exit 退出程序

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值