_cinit
在完成了_setargv() 以及_setenvp() 之后,进入到_cinit 函数。该函数的注释很短,就一句“do C data initialize”,让人完全摸不着头脑。不过不用着急,可以阅读_cinit 函数的实现来加以分析。
_cinit 函数很短,大致上分为三个步骤:
1. _fpmath() 或者 (*_FPinit)();
2. _initterm( __xi_a, __xi_z );
3. _initterm( __xc_a, __xc_z );
第一步 是可选的,_FPinit 主要用来初始化浮点运算。只有当用户写的代码中出现了浮点运算,_FPinit 才会被定义。关于_FPinit 由于MSDN 上没相关资料,在此不做深究。
第二步和第三步 是分别对C和C++程序做初始化。_initterm 接受两个指针作为参数,这两个指针中间的内存区域是一张函数指针表。_initterm 会从第一个指针开始,慢慢向后寻找,直到第二个指针结束,中间如果找到了一块内存表示一个函数指针,则执行该函数。
/* * pointers to initialization sections */ extern _PVFV __xi_a[], __xi_z[]; /* C initializers */ extern _PVFV __xc_a[], __xc_z[]; /* C++ initializers */ void _initterm ( _PVFV * pfbegin, _PVFV * pfend ) { /* * walk the table of function pointers from the bottom up, until * the end is encountered. Do not skip the first entry. The initial * value of pfbegin points to the first valid entry. Do not try to * execute what pfend points to. Only entries before pfend are valid. */ while ( pfbegin < pfend ) { /* * if current table entry is non-NULL, call thru it. */ if ( *pfbegin != NULL ) (**pfbegin)(); ++pfbegin; } }
_initterm( )
先来看看第二步、第三步中都做了什么。这里继续沿用一段空的C代码(main函数中没有任何东西)来build成exe,随后运行该exe,并且OD到_cinit 内部。
这里的两个call 分别表示调用了 _initterm( __xi_a, __xi_z ) 和_initterm( __xc_a, __xc_z ) 。对应的有:
__xi_a = 00406008
__xi_z = 00406010
和
__xc_a = 00406000
__xc_z = 00406004
继续跟进可以发现,在00406008 至 00406010之间仅有一个函数指针,指向__initmbctable()函数。 __initmbctable() 在第(4)篇_setargv 中曾经有过介绍,它会创建一个新的 创建一个新的multibyte code page,前提是之前并没有被创建。但是 _setargv 中已经调用过了该函数, 并将__mbctype_initialized 被设置为1 ,因此这里 __initmbctable()实际上不会重复创建。
在00406000 至 00406004 之间没有函数指针,实际上什么也不执行。
因此,对于空的C程序:
- _initterm( __xi_a, __xi_z ) ------> 调用__initmbctable() ------> 实际上没做什么
- _initterm( __xc_a, __xc_z ) ------> 不产生调用
现在来换一段C程序:
#include <stdio.h> #include <math.h> void main() { int a = 5; double b = sqrt(a); printf("%f\n",b); }
跟踪的结果为:
- _initterm( __xi_a, __xi_z ) ------> 先后调用__initstdio ,__initmbctable
- _initterm( __xc_a, __xc_z ) ------> 不产生调用
这里由于调用了stdio标准库,因此为了stdio能够正确的工作,需要进行初始化。暂时没有找到关于initstdio 函数的资料,暂时不作深究。
至于为什么第二个initterm 不会产生函数调用,这是因为第二个initterm 是用于C++ data initializations,所以在C 程序中毫无作为。来看一段简单的C++ 代码:
int foo(){
int a=1;
int b=9;
return a+b;
}
int a = foo();
void main()
{
}
注意这里的全局变量a,C语言里是不会允许这种写法的,在C中全局变量只能用常量进行赋值。准确说C中的全局变量在编译期就需要被确定,链接器会把所有的全局变量都放进PE的data区域。说白了,这些全局变量都是直接写死在PE中的。
但是C++ 中确允许像上面那样动态赋值,原因就在于第二次调用 initterm 时,会调用foo函数为A进行初始化。对于上面的C++代码:
- _initterm( __xi_a, __xi_z ) ------> 调用__initmbctable() ------> 实际上没做什么
- _initterm( __xc_a, __xc_z ) ------> 调用foo()函数
利用_initterm( __xc_a, __xc_z ) 对C++ data 进行 initialization 有点儿复杂,这里不讨论。
另参考:
http://blog.donews.com/x140yu/archive/2005/05/26/399256.aspx