MSVC CRT的全局构造和析构(1)
来源:互联网 发布:淘宝敏感词排查 编辑:程序博客网 时间:2024/05/19 19:34
11.4.2 MSVC CRT的全局构造和析构(1)
- 摘要:《程序员的自我修养:链接、装载与库》第11章运行库。本章主要介绍运行库的概念、C/C++运行库、Glibc和MSVC CRT、运行库如何实现C++全局构造和析构及以fread()库函数为例对运行库进行剖析。本节为大家介绍MSVC CRT的全局构造和析构。
- 标签:MSVC CRT 程序员 自我修养 程序员的自我修养:链接、装载与库
- Oracle帮您准确洞察各个物流环节
11.4.2 MSVC CRT的全局构造和析构(1)
在了解了Glibc/GCC的全局构造析构之后,让我们趁热打铁来看看MSVC在这方面是如何实现的,有了前面的经验,在介绍MSVC CRT的全局构造和析构的时候使用相对简洁的方式,因为很多地方它们是相通的。
首先很自然想到在MSVC的入口函数mainCRTStartup里是否有全局构造的相关内容。我们可以看到它调用了一个函数为:
mainCRTStartup:
mainCRTStartup()
{
…
_initterm( __xc_a, __xc_z );
…
}
其中__xc_a和__xc_z是两个函数指针,而initterm的内容则是:
mainCRTStartup -> _initterm:
// file: crt/src/crt0dat.c
static void __cdecl _initterm (_PVFV * pfbegin,_PVFV * pfend)
{
while ( pfbegin < pfend )
{
if ( *pfbegin != NULL )
(**pfbegin)();
++pfbegin;
}
}
其中_PVFV的定义是:
typedef void (__cdecl *_PVFV)();
从_PVFV的定义可以看出,它是一个函数指针类型,__xc_a和__xc_z则都是函数指针的指针。不过第一眼看到_initterm这个函数是不是看着很眼熟呢?对照Glibc/GCC的实现,_initterm长得可谓与__do_global_ctors_aux一模一样,它依次遍历所有的函数指针并且调用它们,__xc_a就是这个指针数组的开始地址,相当于__CTOR_LIST__;而__xc_z则是结束地址,相当于__CTOR_END__。
__xc_a和__xc_z不是mainCRTStartup的参数或局部变量,而是两个全局变量,它们的值在mainCRTStartup调用之前就已经正确地设置好了。我们知道mainCRTStartup作为入口函数是真正第一个执行的函数,那么MSVC是如何在此之前就将这两个指针正确设置的呢?让我们来看看__xc_a和__xc_z的定义:
// file: crt/src/cinitexe.c
_CRTALLOC(".CRT$XCA") _PVFV __xc_a[] = { NULL };
_CRTALLOC(".CRT$XCZ") _PVFV __xc_z[] = { NULL };
其中宏_CRTALLOC 定义于crt/src/sect_attribs.h:
……
#pragma section(".CRT$XCA",long,read)
#pragma section(".CRT$XCZ",long,read)
……
#define _CRTALLOC(x) __declspec(allocate(x))
在这个头文件里,须要注意的是两条pragma指令。形如#pragma section的指令语法如下:
#pragma section( "section-name" [, attributes] )
作用是在生成的obj文件里创建名为section-name的段,并具有attributes属性。因此这两条pragma指令实际在obj文件里生成了名为.CRT$XCA和.CRT$XCZ的两个段。下面再来看看_CRTALLOC这个宏,该宏的定义为__declspec(allocate(x)),这个指示字表明其后的变量将被分配在段x里。所以__xc_a被分配在段.CRT$XCA里,而__xc_z被分配在段.CRT$XCZ里。
现在我们知道__xc_a和__xc_z分别处于两个特殊的段里,那么它是如何形成一个存储了初始化函数的数组呢?当编译的时候,每一个编译单元都会生成名为.CRT$XCU(U是User的意思)的段,在这个段中编译单元会加入自身的全局初始化函数。当链接的时候,链接器会将所有相同属性的段合并,值得注意的是:在这个合并过程中,所有输入的段在被合并到输出段时,是据字母表顺序依次排列。于是在本例中,各个段链接之后的状态可能如图11-11所示。
由于.CRT$XT*这些段的属性都是只读的,且它们的名字很相近,所以它们会被按顺序合并到一起,最后往往被放到只读段中,成为.rdata段的一部分。这样就自然地形成了存储所有全局初始化函数的数组,以供_initterm函数遍历。我们不得不再次惊叹!MSVCCRT的全局构造实现在机制上与Glibc基本是一样的,只不过它们的名字略有不同,MSVCCRT采用这种段合并的模式与.ctor的合并及__CTOR_LIST__和__CTOR_END__的地址确定何其相似!这再一次证明了虽然各个操作系统、运行库、编译器在细节上大相径庭,但是在基本实现的机制上其实是完全相通的。
(点击查看大图)图11-11 PE文件的初始化部分【小实验】
自己添加初始化函数:
#include <iostream>
#define SECNAME ".CRT$XCG"
#pragma section(SECNAME,long,read)
void foo()
{
std::cout << "hello" << std::endl;
}
typedef void (__cdecl *_PVFV)();
__declspec(allocate(SECNAME)) _PVFV dummy[] = { foo };
int main()
{
return 0;
}
回书目 上一节 下一节
11.4.2 MSVC CRT的全局构造和析构(2)
http://book.51cto.com 2009-04-22 16:12 俞甲子/石凡/潘爱民 电子工业出版社 我要评论(0)
* 摘要:《程序员的自我修养:链接、装载与库》第11章运行库。本章主要介绍运行库的概念、C/C++运行库、Glibc和MSVC CRT、运行库如何实现C++全局构造和析构及以fread()库函数为例对运行库进行剖析。本节为大家介绍MSVC CRT的全局构造和析构。
* 标签:MSVC CRT 程序员 自我修养 程序员的自我修养:链接、装载与库
*
Oracle帮您准确洞察各个物流环节
11.4.2 MSVC CRT的全局构造和析构(2)
运行这个程序,可以得到如"hello"的输出。为了验证A~Z的这个字母表排列,读者可以修改SECNAME,使之不处于.CRT$XCA 和.CRT$XCZ之间,理论上不会得到任何输出。而如果将段名改为.CRT$XCV(V的字典序在U之后),那么foo函数将在main执行之后执行。
MSVC CRT 析构
最后来看看MSVC的全局析构的实现,在MSVC里,只需要在全局变量的定义位置上设置一个断点,就可以看到在.CRT$XC?中定义的全局初始化函数的内容。我们仍然使用本章一开头的HelloWorld来作为示例:
#include <iostream>
class HelloWorld
{
public:
HelloWorld() {std::cout << "hi/n";}
~HelloWorld(){std::cout << "bye/n";}
};
HelloWorld Hw;
int main()
{
return 0;
}
这里在加粗的位置上设置断点。运行程序并中断之后查看反汇编可以得到初始化函数的内容:
011B1B70 mov eax,dword ptr [__imp_std::cout (11B2054h)]
011B1B75 push offset string "hi/n" (11B2124h)
011B1B7A push eax
011B1B7B call std::operator<<<std::char_traits<char> > (11B1140h)
011B1B80 push offset `dynamic atexit destructor for 'Hw'' (11B1B90h)
011B1B85 call atexit (11B13B0h)
011B1B8A add esp,0Ch
011B1B8D ret
在这里可以看见这段程序首先调用了内联之后的HelloWorld的构造函数,然后和 g++相同,调用atexit将一个名为dynamic atexit destructor for 'Hw''的函数注册给程序退出时调用。而这个dynamic atexit destructor for 'Hw''函数的定义也能很容易找到:
`dynamic atexit destructor for 'Hw'':
011B1B90 mov eax,dword ptr [__imp_std::cout (11B2054h)]
011B1B95 push offset string "bye/n" (11B2128h)
011B1B9A push eax
011B1B9B call std::operator<<<std::char_traits<char> > (11B1140h)
011B1BA0 add esp,8
011B1BA3 ret
可以看出,这个函数的作用就是在对象Hw调用内联之后进行析构。看到这里,我想各位读者肯定有跟我一样的心情,那就是希望举一反三的愿望并不是不切实际的,它是实实在在存在的。Glibc下通过__cxa_exit()向exit()函数注册全局析构函数;MSVC CRT也通过atexit()实现全局析构,它们除了函数命名不同之外几乎没有区别。
【责任编辑:云霞 TEL:(010)68476606】
回书目 上一节 下一节
- MSVC CRT的全局构造和析构(1)
- MSVC CRT的全局构造和析构
- MSVC與CRT的恩怨情仇
- MSVC與CRT的恩怨情仇
- C++ 全局对象构造和析构
- MSVC 与 CRT 之间的恩怨情仇
- 深度剖析C++全局构造函数和析构函数的调用机制
- C++带有虚函数的单继承类的构造过程探索,msvc和gcc编译器
- 建立全局和局部对象时,不同的构造函数和析构函数的调用顺序
- 关于全局全局静态局部局部静态的构造与析构顺序
- 全局类对象、静态全局类对象、静态成员类对象、静态局部类对象 的构造和析构过程
- MSVC CRT运行库启动代码分析
- 全局构造函数与析构函数
- glibc与MSVC CRT,crt编译错误及解决
- 关于c++构造函数、析构函数在全局实例(global)和在局部实例先后顺序
- 程序入口函数和glibc及C++全局构造和析构
- 程序入口函数和glibc及C++全局构造和析构
- MSVC和MinGW的DLL工具
- glibc与MSVC CRT,crt编译错误及解决
- 我的输入法上哪去了?
- delphi 子窗体最大化
- prototype 属性使用说明
- Windows Mobile下WinInet的异步使用方法 (转)
- MSVC CRT的全局构造和析构(1)
- TDI code 1
- 工作
- java、jvm与.net
- 使用 Windows CE .NET 测试工具包 (CETK) 构建和测试设备
- java.lang.ClassNotFoundException: oracle.jdbc.driver.OracleDriver 错误的解决办法
- Javascript跨域访问解决方案【转帖】
- 经济什么时候能好转
- Best Practices for Creating DLLs