11.1.2 入口函数如何实现(3)
这是入口函数的头部。下面的代码出现于该函数的开头,显得杂乱无章。不过其中关键的内容是给一系列变量赋值:
posvi = (OSVERSIONINFOA *)_alloca(sizeof(OSVERSIONINFOA)); if ( _osplatform != VER_PLATFORM_WIN32_NT ) _winver = (_winmajor << 8) + _winminor; |
被赋值的这些变量,是VC7里面预定义的一些全局变量,其中_osver和_winver表示操作系统的版本,_winmajor是主版本号,更具体的可以查阅MSDN。这段代码通过调用GetVersionExA(这是一个Windows API)来获得当前的操作系统版本信息,并且赋值给各个全局变量。
为什么这里为posvi分配内存不使用malloc而使用alloca呢?是因为在程序的一开始堆还没有被初始化,而alloca是唯一可以不使用堆的动态分配机制。alloca可以在栈上分配任意大小的空间(只要栈的大小允许),并且在函数返回的时候会自动释放,就好像局部变量一样。
由于没有初始化堆,所以很多事情没法做,当务之急是赶紧把堆先初始化了:
if ( !_heap_init(0) ) |
__try {_acmdln = (char *)GetCommandLineA(); if ( _setargv() < 0 ) if ( _setenvp() < 0 ) initret = _cinit(TRUE); if (initret != 0) mainret = main(__argc, __argv, _environ); _cexit(); __except ( _XcptFilter(GetExceptionCode(), GetExceptionInformation()) ) |
在这里是一个Windows的SEH的try-except块,里面做了什么呢?首先使用_ioinit函数初始化了I/O,接下来这段代码调用了一系列函数进行各种初始化,包括:
_setargv:初始化main函数的argv参数。
_setenv:设置环境变量。
_cinit:其他的C库设置。
在最后,可以看到函数调用了main函数并获得了其返回值。try-except块的except部分是最后的清理阶段,如果try块里的代码发生异常,则在这里进行错误处理。最后退出并返回main的返回值。
try-except块
try-except块是Windows结构化异常处理机制SEH的一部分。try-except块的使用方法如下:
_start -> __libc_start_main -> exit: void exit (int status) |
__try { |
当code 1出现异常(段错误等)的时候,except部分的code 2会执行以异常处理。更为详细的信息请查阅MSDN。
总结一下,这个mainCRTStartup的总体流程就是:
(1)初始化和OS版本有关的全局变量。
(2)初始化堆。
(3)初始化I/O。
(4)获取命令行参数和环境变量。
(5)初始化C库的一些数据。
(6)调用main并记录返回值。
(7)检查错误并将main的返回值返回。
事实上还是MSVC的入口函数的思路较为清晰。在第13章里,我们将仿照VC入口函数的思路实现一个Linux下的简易入口函数。
Q&A
Q:msvc的入口函数使用了alloca,它是如何实现的。
A:alloca函数的特点是它能够动态地在栈上分配内存,在函数退出时如同局部变量一样自动释放。结合之前我们介绍的函数标准进入和退出指令序列就知道,函数退出时的退栈操作是直接将ESP的值赋为EBP的值。因此不管在函数的执行过程中ESP减少了多少,最后也能够成功地将函数执行时分配的所有栈空间回收。在这个基础上,alloca的实现就非常简单,仅仅是将ESP减少一定数值而已。
Q:为什么MSVC的Win32程序的入口使用的是WinMain?
A:WinMain和main一样,都不是程序的实际入口。MSVC的程序入口是同一段代码,但根据不同的编译参数被编译成了不同的版本。不同版本的入口函数在其中会调用不同名字的函数,包括main/wmain/WinMain/wWinMain等。
| 回书目 上一节 下一节 |