当前位置: 首页 > article >正文

C++全局构造和初始化

片段摘自程序员的自我修养—链接、装载与库.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_aux

void __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


http://www.kler.cn/a/394818.html

相关文章:

  • 如何保证Redis与MySQL双写一致性
  • 单片机设计电流与温度监控python上位机监控平台设计
  • 第 13 章 -Go 语言 接口
  • 阿里云centos7.9服务器磁盘挂载,切换服务路径
  • Ceph 中Crush 算法的理解
  • HAproxy 详解
  • 算法训练(leetcode)二刷第二十五天 | *134. 加油站、*135. 分发糖果、860. 柠檬水找零、*406. 根据身高重建队列
  • 24/11/14 算法笔记 EM算法期望最大化算法
  • CentOS网络配置
  • 利用飞书多维表格自动发布版本
  • Matlab实现麻雀优化算法优化随机森林算法模型 (SSA-RF)(附源码)
  • 【Android、IOS、Flutter、鸿蒙、ReactNative 】标题栏
  • Isaac Sim+SKRL机器人并行强化学习
  • 【Android、IOS、Flutter、鸿蒙、ReactNative 】文本点击事件
  • 【CICD】GitLab Runner 和执行器(Executor
  • 通过PHP创建AWS的CloudFront并绑定证书添加备用域名
  • sql server创建固定的链路服务器
  • kafka:使用flume自定义拦截器,将json文件抽取到kafka的消息队列(topic)中,再从topic中将数据抽取到hdfs上
  • 麒麟服务器工作站SP1 arm环境qt5.6.3源码编译
  • 如何处理 iOS 客户端内 Webview H5 中后台播放的音视频问题
  • Mac 使用mac 原生工具将mp4视频文件提取其中的 mp3 音频文件
  • git config是做什么的?
  • 如何在 Ubuntu 22.04 上安装 ownCloud
  • 数字IC后端实现之Innovus specifyCellEdgeSpacing和ICC2 set_placement_spacing_rule的应用
  • 低代码可视化-uniapp开关选择组件-低码生成器
  • 理解 C++ 中的 `const` 关键字