微信网站建设教程成人教育机构排行前十名
片段摘自程序员的自我修养—链接、装载与库.pdf
11.4
程序在进入main之前,需要对全局对象进行构造初始化。
glibc全局对象进行构造初始化
gibc启动程序时会经过.init
段,退出程序时会经过.finit
段。这两个段中的代码最终拼接成_init()
和_finit()
,这两个函数优先于/后于main
函数执行。
_start->_init()->main()->_finit()
用户所有放在.init
段的代码将在main
函数之前执行,即全局对象的初始化。具体执行流程如下:
_start->__libc_start_main->__libc_csu_init->_init->__do_global_ctors_auxvoid __do_global_ctors_aux(void)
{/*call constructor functions*/unsigned long nptrs = (unsigned long)__CTOR_LIST__[0];
for(unsigned i = nptrs;i >= 1;i--)__CTOR_LIST__[i]
}
CTOR_LIST 存放所有全局对象的构造和初始化的函数指针。__do_global_ctors_aux从__CTOR_LIST__
的下一个位置开始,按顺序执行函数指针,直到遇上NULL(__CTOR_END__
),从而调用所有全局对象的构造函数。
那么__CTOR_LIST__
是如何初始化的呢?
GCC编译器在编译的时候会遍历每个编译单元的全局对象,生成一个特殊函数_GLOBAL__I_HW
,利用这个特殊函数对本编译单元的所有全局、静态对象进行初始化。
static void _GLOBAL__I_HW(void)
{Hw::Hw();//构造对象atexit(__tcf_l)
}
一旦目标文件里面有这样的函数,编译器则会在这个编译单元目标文件的.ctors
放置一个指针,指向_GLOBAL__I_HW
。即__CTOR_LIST__
列表保存的是每个编译单元的_GLOBAL__I_HW
函数指针。链接器则会将所有的目标文件的.ctors
合并成一个.ctors段
__CTOR_LIST__
代表所有.ctors
的起始地址。
__CTOR_END__
指向.ctors
段的末尾。
所以如果想在main之前调用函数
,只需要在.ctors
段里面添加函数指针即可。
#include <stdio.h>
#include <stdlib.h>//main函数之前执行
__attribute((constructor)) void before_main()
{printf("====%s=====\n", __FUNCTION__);
}//main函数之后执行
__attribute((destructor)) void after_main()
{printf("====%s=====\n", __FUNCTION__);
}int main(int argc, char **argv)
{printf("=====entering %s.========\n", __FUNCTION__);printf("====exiting from main!===\n");return 0;
}
MSVC 全局对象进行构造初始化
msvc的入口函数mainCRTStartup
mainCRTStartup()
{..._initterm(__xc_a,__xc_z);...
}
其中__xc_a
和__xc_z
是两个函数指针。而_initterm
的内容则是:
typedef void (__cdecl *_PVFV)();
static void __cdecl _initterm(_PVFV* pfbegin,_PVFV* pfend)
{while(pfbegin < pfend){if(*pfbegin != null)(**pfbegin)();++pfbegin;}
}
第一眼看上去和glibc几乎一模一样,__xc_a
相当于指针的开始地址__CTOR_LIST__
,__xc_z
则相当于结束地址__CTOR_END__
那么msvc 是如何初始化__xc_a
和__xc_z
的呢?
_CRTALLOC(".CRT$XCA") _PVFV __xc_a[] = {NULL};
_CRTALLOC(".CRT$XCZ") _PVFV __xc_Z[] = {NULL};
其中_CRTALLOC
定义
#pragma section(".CRT$XCA",long,read)
#pragma section(".CRT$XCZ",long,read)#define _CRTALLOC(x) __declspec(allocate(x))
#pragma section("section-name",[,attributes])
的作用是在obj
的文件里创建名为section-name
的段
,并具有指定的attributes属性。所以上述两条指令实际生成了两个名为.CRT$XCA、.CRT$XCZ
的段。__declspec(allocate(x)
则表示变量将变量分配在段x
里。所以__xc_a
分配在.CRT$XCA
、__xc_z
分配在.CRT$XCZ
里。
msvc编译的时候,会为每一个编译单元生成.CRT$XCU
(U是User的意思)的段。在这个段里面加入自身的全局初始化函数。链接的时候将所有相同属性的段合并。段根据字母A-Z的顺序排序
。
所以如果想在main之前调用函数
,只需要在段(A-Z之间,不包含A,Z
).CRT$XCB
里面添加函数指针即可。
因为全局初始化函数在段
.CRT$XCU
里面,根据A-Z排序并执行。所以如果想要在全局构造函数之前执行,段名必须在U
之前。如果想在全局构造函数之后main之前则段名必须在U
之后。不包含A,Z
class CA
{
public:CA(){printf("CA::CA\r\n");}~CA(){printf("CA::~CA\r\n");}
};#define SECNAME ".CRT$XCB"#pragma section(SECNAME,long,read)
void foo()
{printf("foo \r\n");
}
typedef void(_cdecl* _PVFV)();
_declspec(allocate(SECNAME)) _PVFV dummy[] = { foo };CA g_obj;
int main()
{printf("main enter\r\n");printf("main exit\r\n");return 0;
}
.CRT$XCB
段输出(先执行foo,在执行全局对象初始化,在执行main
)
.CRT$XCY
段输出(先执行全局对象初始化,在执行foo,在执行main
)
Note:注意事项,如果在main函数之前想引用全局变量或者函数,此时全局变量的构造函数还没执行。可能结果和预期不一致。
demo: foo里面改变CA成员变量的值。
CA g_obj;void foo()
{g_obj.m_a = 10;printf("foo g_obj addr:%I64d g_obj.m_a=%d\r\n", &g_obj, g_obj.m_a);
}int main()
{printf("main enter\r\n");printf("foo g_obj addr:%I64d g_obj.m_a=%d\r\n", &g_obj, g_obj.m_a);printf("main exit\r\n");return 0;
}
虽然在foo里面改变了m_a
,但是稍后执行段.CRT$XCU
时此时会调用CA::CA
导致m_a被重新赋值0