深度剖析C++全局构造函数和析构函数的调用机制
来源:互联网 发布:java web实时应用程序 编辑:程序博客网 时间:2024/06/06 18:15
控制台EXE中C++的全局变量在main之前初始化,在main之后清除,VC编译器、链接器和VC运行库代码互相配合完成了这个魔术。请复制这段代码到你新建的控制台程序,创建并运行:
#include "stdafx.h"
#include <iostream>
#define SECNAME ".CRT$XCU"
#pragma section (SECNAME, long, read)
void Cleanup()
{
std::cout << "bye" << std::endl;
}
void Init()
{
//do anything you want.
std::cout << "hello" << std::endl;
atexit(Cleanup);//注册析构函数
}
typedef void (_cdecl *_PVFV) () ;
_declspec (allocate(SECNAME)) _PVFV callB4Main[] = { Init };
int main()
{
std::cout << "in main" << std::endl;
return 0;
}
输出结果:
hello
in main
bye
奇怪!你并没有调用Init和Cleanup,但很明显它们被调用了,这是怎么回事呢?
我们知道真正的入口函数是mainCRTStartup,对于我们的讨论,mainCRTStartup可简化如下:
int __tmainCRTStartup()//这个函数会被链接进我们的exe,并作为入口点函数
{
_initterm();
int result = main();
exit();
return result;
}
// file: crt\src\crt0dat.c
static void _cdecl _initterm (_PVFV * pfbegin,_PVFV * pfend)
{
while ( pfbegin < pfend )
{
if ( *pfbegin != NULL )
(* *pfbegin) ();
++pfbegin;
}
}
这就是调用构造函数的地方。对于我们定义的每一个全局变量,编译器把它的构造函数调用(不管你带多少参数)编译成一段代码(签名格式为_PVFV),这段代码同时用atexit注册了析构函数(对于编译器这是很容易的)。然后把这段代码的函数指针放入一个_PVFV数组。链接器把这些_PVFV数组连成一个大数组。
_initterm参数中的pfbegin就指向这个数组的第一个元素,pfend指向这个数组的最后一个元素(总为NULL),我们的构造函数就这样被调用了,这也是上面的魔术的工作原理。atexit注册的函数将在main之后exit(与_initterm代码相似)中执行,而且遵循LIFO的原则,所以析构函数会按照和构造函数相反的顺序调用。
对于非控制台EXE和DLL,情形是类似的。
任何用户态进程的真正的入口其实是kernel32.dll!_BaseProcessStart@4,但这与我们的讨论无关。
参考书籍:
《程序员的自我修养--装载、链接和库》
#include "stdafx.h"
#include <iostream>
#define SECNAME ".CRT$XCU"
#pragma section (SECNAME, long, read)
void Cleanup()
{
std::cout << "bye" << std::endl;
}
void Init()
{
//do anything you want.
std::cout << "hello" << std::endl;
atexit(Cleanup);//注册析构函数
}
typedef void (_cdecl *_PVFV) () ;
_declspec (allocate(SECNAME)) _PVFV callB4Main[] = { Init };
int main()
{
std::cout << "in main" << std::endl;
return 0;
}
输出结果:
hello
in main
bye
奇怪!你并没有调用Init和Cleanup,但很明显它们被调用了,这是怎么回事呢?
我们知道真正的入口函数是mainCRTStartup,对于我们的讨论,mainCRTStartup可简化如下:
int __tmainCRTStartup()//这个函数会被链接进我们的exe,并作为入口点函数
{
_initterm();
int result = main();
exit();
return result;
}
// file: crt\src\crt0dat.c
static void _cdecl _initterm (_PVFV * pfbegin,_PVFV * pfend)
{
while ( pfbegin < pfend )
{
if ( *pfbegin != NULL )
(* *pfbegin) ();
++pfbegin;
}
}
这就是调用构造函数的地方。对于我们定义的每一个全局变量,编译器把它的构造函数调用(不管你带多少参数)编译成一段代码(签名格式为_PVFV),这段代码同时用atexit注册了析构函数(对于编译器这是很容易的)。然后把这段代码的函数指针放入一个_PVFV数组。链接器把这些_PVFV数组连成一个大数组。
_initterm参数中的pfbegin就指向这个数组的第一个元素,pfend指向这个数组的最后一个元素(总为NULL),我们的构造函数就这样被调用了,这也是上面的魔术的工作原理。atexit注册的函数将在main之后exit(与_initterm代码相似)中执行,而且遵循LIFO的原则,所以析构函数会按照和构造函数相反的顺序调用。
对于非控制台EXE和DLL,情形是类似的。
任何用户态进程的真正的入口其实是kernel32.dll!_BaseProcessStart@4,但这与我们的讨论无关。
参考书籍:
《程序员的自我修养--装载、链接和库》
- 深度剖析C++全局构造函数和析构函数的调用机制
- 建立全局和局部对象时,不同的构造函数和析构函数的调用顺序
- [C++]显示调用构造函数和析构函数
- 全局对象和函数内静态对象调用构造析构函数差异
- 全局对象和函数内静态对象调用构造析构函数差异
- 全局对象构造函数的调用时机
- C++ 全局构造函数调用的顺序
- 构造函数、拷贝构造函数和析构函数的的调用时刻及调用顺序
- 构造函数、拷贝构造函数和析构函数的的调用时刻及调用顺序
- 构造函数、拷贝构造函数和析构函数的的调用时刻及调用顺序
- 构造函数、拷贝构造函数和析构函数的的调用时刻及调用顺序
- 深度剖析空间配置器(一)构造和析构函数
- 派生类构造函数和析构函数的调用顺序(C++)
- <C++>13.构造函数和析构函数的调用顺序
- 深度剖析函数的调用过程
- 构造函数和析构函数的调用
- 构造函数和析构函数的调用次序
- 关于构造函数和析构函数的调用顺序
- framebuffer 一些基本知识
- SVN常用命令
- Android 视频播放器 应用层设计 考虑事项 (二)
- 自定义异常
- SharePoint学习笔记001:访问站点列表碰到的一些问题
- 深度剖析C++全局构造函数和析构函数的调用机制
- 数据库主键设计之思考
- [转载]中文搜索引擎技术揭密:中文分词
- 内部类学习
- log4j的用法,控制不同场景的日志输出
- Hibernate中对象的三种状态及相互转化
- bmp格式
- Struts2+Hibernate+Spring 整合示例
- 文件锁