【问题】
最近在封装一个底层库的时候遇到诡异的问题,用gcc4.1.2编译的程序一切正常,
用gcc4.8.2编译的程序运行总是coredump。 经过分析后发现是初始化顺序问题,代码如下(经过简化):
typedef std::map<std::string, int> typeid_t;
typeid_t TYPEID;
__attribute__((constructor)) void init()
{
TYPEID["http"] = 2;
}
原因:之所以出现coredump,是因为在执行init函数的时候变量TYPEID还根本没有初始化!
【问题代码背景】
一个底层库需要做一些初始化操作,这个初始化需要隐式地自动地进行,需要对上层使用者透明,
而且需要在程序进入main函数之前自动初始化完成。
【解决方法】
想到的第一个解决方法:用指针而不是全局变量,规避初始化顺序的问题:
typedef std::map<std::string, int> typeid_t;
typeid_t* TYPEID;
__attribute__((constructor)) void init()
{
TYPEID = new typeid_t;
(*TYPEID)["http"] = 2;
}
这种方法可以解决问题,但是怎么看怎么别扭,总觉得不够上流,而且TYPEID引用起来也麻烦。
试着将constructor属性用在变量上面,结果编译错误了。开始回忆,记忆中可以给全局变量指定
初始化优先级,只是记不清那个属性的名字,于是翻了一下gcc手册,找到init_priority属性可以
用来干这个事情。理论上只要给init_priority指定一个比constructor更小的数字就行了(数字越
小,优先级越高)
试验第二种方法:
__attribute__((init_priority(1))) std::map<std::string, int> TYPEID;
__attribute__((constructor(2))) void init()
{
TYPEID["http"] = 2;
}
用gcc4.8.2编译正常,但是用gcc4.1.2编译不正常。于是翻了一下gcc4.1.2的手册,发现gcc4.1.2
的constructor属性根本不支持参数! :(
问题貌似搞不定呢? 能否在不给constructor传递参数的前提下解决这个问题呢?如果不给constructor
传递参数,constructor有默认参数吗?有的话"默认" 的参数应该是多少呢?
在回答这个问题之前,先看看优先级的数字范围。init_priority和constructor的参数用以指定变量或者函数
的初始化优先级,这个参数是一个unsigned short整数, 范围是[0, 65535], 但是gcc不让用0,所以真正可以
使用的数字范围是[1, 65535]。注意数字越小,优先级越高。
回到之前那个问题,经过测试后,发现constructor不传递参数(gcc4.1.2压根儿传不了)的时候确实是有默认参
数的,而且这个默认参数就是区间的最大值65535. 有了这个依据,问题就好办了,只需要给TYPEID的
init_priority属性传递一个小于65535的整数就行了, 代码如下:
typedef std::map<std::string, int> typeid_t;
__attribute__((init_priority(65534))) typeid_t TYPEID;
__attribute__((constructor)) void init()
{
TYPEID["http"] = 2;
}
至此,以上代码在gcc4.1.2和gcc4.8.2都可以正常运行了!
【总结】
为了确保 '全局变量/对象 在 __attribute__((constructor))函数 之前初始化', 需要给全局变量加上初始化优先级,
另外gcc4.1.2的constructor不支持参数,为了gcc4.1.2/gcc4.8.2都能正确工作,需要这样:
__attribute__((init_priority(65534))) std::map<std::string, int> TYPEID;
__attribute__((constructor)) void init()
{
TYPEID["http"] = 2;
}
1. constructor的默认优先级是65535,属性值区间是[1,65535], 用[1, 100]这个区间的值也可以,
但是编译有警告,因为[1, 100]是留给gcc内部使用的,所以最好使用[101, 65535]这个区间。
2. 上面init_priority的参数为65534,实际上可以是[101,65534]内的任意一个值(gcc4.1.2即便传65535
一样可以,但是gcc4.8.2不行,所以为了兼顾gcc的2个版本,必须要小于65535)
3. gcc4.1.2编译:全局变量/对象 默认在__attribute__((constructor))函数之"前"初始化,
如果是gcc4.1.2,则根本不需要给TYPEID指定init_priority属性。保证始终在constructor函数前执行。
4. gcc4.8.2编译:全局变量/对象 默认在__attribute__((constructor))函数之"后"初始化