中通服建设有限公司网站,优秀网页 网站,网页设计排版布局,手机网站建设图片素材片段摘自程序员的自我修养—链接、装载与库.pdf 11.4
程序在进入main之前#xff0c;需要对全局对象进行构造初始化。
glibc全局对象进行构造初始化
gibc启动程序时会经过.init段#xff0c;退出程序时会经过.finit段。这两个段中的代码最终拼接成_init()和_finit(),这两个…片段摘自程序员的自我修养—链接、装载与库.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