设为首页 加入收藏

TOP

11.1.2 入口函数如何实现(3)
2013-10-07 00:45:05 来源: 作者: 【 】 浏览:59
Tags:11.1.2 入口 函数 如何 实现

11.1.2  入口函数如何实现(3)

这是入口函数的头部。下面的代码出现于该函数的开头,显得杂乱无章。不过其中关键的内容是给一系列变量赋值:

    posvi = (OSVERSIONINFOA *)_alloca(sizeof(OSVERSIONINFOA));
posvi->dwOSVersionInfoSize = sizeof(OSVERSIONINFOA);

GetVersionExA(posvi);
_osplatform = posvi->dwPlatformId;
_winmajor = posvi->dwMajorVersion;
_winminor = posvi->dwMinorVersion;
_osver = (posvi->dwBuildNumber) & 0x07fff;
    if ( _osplatform != VER_PLATFORM_WIN32_NT )
_osver |= 0x08000;
    _winver = (_winmajor << 8) + _winminor;

被赋值的这些变量,是VC7里面预定义的一些全局变量,其中_osver和_winver表示操作系统的版本,_winmajor是主版本号,更具体的可以查阅MSDN。这段代码通过调用GetVersionExA(这是一个Windows API)来获得当前的操作系统版本信息,并且赋值给各个全局变量。

为什么这里为posvi分配内存不使用malloc而使用alloca呢?是因为在程序的一开始堆还没有被初始化,而alloca是唯一可以不使用堆的动态分配机制。alloca可以在栈上分配任意大小的空间(只要栈的大小允许),并且在函数返回的时候会自动释放,就好像局部变量一样。

由于没有初始化堆,所以很多事情没法做,当务之急是赶紧把堆先初始化了:

if ( !_heap_init(0) )            
fast_error_exit(_RT_HEAPINIT);
这里使用_heap_init函数对堆(heap)进行了初始化,如果堆初始化失败,那么程序就直接退出了。
__try {
if ( _ioinit() < 0 )
_amsg_exit(_RT_LOWIOINIT);
        _acmdln = (char *)GetCommandLineA();
_aenvptr = (char *)__crtGetEnvironmentStringsA();
        if ( _setargv() < 0 )
_amsg_exit(_RT_SPACEARG);
        if ( _setenvp() < 0 )
_amsg_exit(_RT_SPACEENV);
        initret = _cinit(TRUE);       
        if (initret != 0)
_amsg_exit(initret);
__initenv = _environ;
        mainret = main(__argc, __argv, _environ);
        _cexit();
}
__except ( _XcptFilter(GetExceptionCode(), GetExceptionInformation()) )
{
mainret = GetExceptionCode();
_c_exit();
} /* end of try - except */
return mainret;
}

在这里是一个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)
{
while (__exit_funcs != NULL)
{
...
__exit_funcs = __exit_funcs->next;
}
...
_exit (status);
}
其中__exit_funcs是存储由__cxa_atexit和atexit注册的函数的链表,而这里的这个while循环则遍历该链表并逐个调用这些注册的函数,由于其中琐碎代码过多,这里就不具体列出了。最后的_exit函数由汇编实现,且与平台相关,下面列出i386的实现:
__try {
code 1
}
__except(...) {
code 2
}

当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等。

【责任编辑:云霞 TEL:(010)68476606】

回书目   上一节   下一节

】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
分享到: 
上一篇11.2.1 C语言运行库 下一篇11.1.2 入口函数如何实现(2)

评论

帐  号: 密码: (新用户注册)
验 证 码:
表  情:
内  容: