分享两个强符号,弱符号引起的编译问题
分享两个强符号,弱符号引起的编译问题
由于SP的编译分为debug和release两种模式(话说也没有谁不这么编吧),往往在debug开发的时候没有遇到的问题,在release阶段暴露了,这里面最容易出现的就是弱符号丢失,导致符号定位出错或者符号没找到的问题。
两个真实的案例
Case1: 模板类的静态函数特例化问题
这个场景是这样的,一个模版类有一个可以特例化的静态初始函数,不同的模版可以去特例化自己的初始化函数。
foo.h文件中定义了一个模板类和一个静态方法,这个方法有一个默认的实现。
//////// foo.h /////// template<typename T> class foo{ public: static void init(){ return; } }; template<> //增加int模版的声明,但是没有增加float模版的 void foo<int>::init();
foo_int.cpp 文件中有一个int类型的特例化方法的实现
/////// foo_int.cpp /////// #include "foo.h" template<> void foo<int>::init(){ printf("init int foo"); }
foo_float.cpp 文件定义了一个float类型的特例化方法的实现
/////// foo_float.cpp /////// #include "foo.h" template<> void foo<float>::init(){ printf("init float foo"); }
main.cpp 文件定义了一个函数分别调用两个方法
////// main.cpp /////// #include "foo.h" int main(){ foo<int>::init(); foo<float>::init(); }
time.h文件中定义了一个静态函数
//////// time.h ////////// class time{ public: static int getTime(); }
time.cpp文件中把这个函数声明为内联函数,cpp中的内联函数只在当前cpp文件内展开。
//////// time.cpp ////////// inline int getTime(){ return 0; }
main.cpp文件中调用这个getTime函数
//////// main.cpp ////////// #include "time.h" int main(){ time::getTime(); }
上述程序在debug模式下会同时执行foo<int>和foo<float>两个foo模版的的init函数,在release模式下只会执行foo<int>模版的init函数
Case2: CPP里的内联函数展开问题
这个场景是这样的,一个内联函数定义在了cpp文件内部
上述程序在debug模式下是能编译通过的,在release模式下提示getTime找不到。
强符号和弱符号的定义,规则与使用
什么是强符号和弱符号
教科书上是这么写的:
编译时,编译器向汇编器输出每个全局符号,或者是强,或者是弱,而汇编器把这 个信息隐含的编码在重定位目标文件的符号表里。
我们用readelf查看目标文件:
LOCAL 开头的是局部符号,这些符号只能在当前目标文件可见,无法给别人使用。
GLOBAL 开头的都是全局强符号,这些符号,可以被外部目标文件访问。
WEAK 开头的就是全局弱符号,这些符号可以引用,但是是可以被改写的。
简单说来:
已初始化的全局变量是强符号 strong symbol
未初始化的全局变量是弱符号 weak symbol
这两个东西在链接时候的使用规则
不允许有多个强符号
简单来说,就是重复定义,嗯,就这么简单。
如果一个强符号和多个弱符号同时存在,那么使用强符号
这个也很简单了,如果出现重复定义,比如两个全局变量,第一个初始化了,第二个没有, 那么第二个变量所指向的符号其实是第一个符号,这是一个经常遇见的坑。所以自定义全 局变量最好初始化掉,最起码冲突了编译器会报错,不是么。最好在编译时指定-fno-common 参数,会警告那些重复出现的符号,无论强弱。
如果有多个弱符号,那么选择使用弱符号中占用空间最大的那一个
这个和教科书上写的不太一致了,教科书上说的是任意选取一个。假如,弱符号占用的空 间比强符号大,怎么办?这时候仍然选择强符号,同时ld会弹一个警告。
弱符号在静态链接或者动态链接中都不会生效,以下是gcc里面的一段说明
When the link editor searches archive libraries [see ``Archive File'' in Chapter 7], it extracts archive members that contain definitions of undefined global symbols. The member's definition may be either a global or a weak symbol. The link editor does not extract archive members to resolve undefined weak symbols. Unresolved weak symbols have a zero value.
为什么要定义这两个东西
弱符号允许相同符号名出现
声明为弱符号的函数或变量,用户可以去改写他,类似于c++里面的重载
弱符号可有可无,可以给程序更多的扩展
///////// 如果有定义 foo 函数,就调用;没有就拉倒 ///////// extern void foo(void) __attribute__((weak)); void fun(void) { if (foo) foo(); } int main(){ func(); return 0; }
回头解释一下上述两个case
上述两个case其实是一个原因,弱符号机制把问题屏蔽了。
第一个case中,debug模式下,main.o会生成float模版类的弱符号表,而foo_float.o中是有这个函数的强符号实现的,所以最终会被强符号替换掉。而release模式下,头文件没有声明这个函数的实现,所以不会产生这个弱符号表,直接用默认的init函数去替换到代码段内,导致即使定义了强符号的实现但压根就没被执行。
//debug模式下的man.o U _ZN4fooIfE7initEv 0000000000000000 W _ZN4fooIiE7initEv //release模式下的man.o U _ZN4fooIiE7initEv
第二个case中,debug模式下,cpp的内联函数不会被展开,同时在符号表中是弱符号,所以外部调用的时候会优先选择这个弱符号。而release模式下,cpp的内联被展开了,头文件中的定义就找不到对应的实现了,所以编译错误。
//debug模式下的time.o 0000000000000000 W _ZN4time7getTimeEv
Cm2对Zookeeper的改造
zookeeper客户端提供这样一个日志函数,这个函数会把日志信息打印的到标准输出:
日志信息打满屏幕太乱,
zookeeper是CM2的一个重要组成部分需要有日志记录追踪他一个一举一动,
铁丑同学娴熟的使用弱符号,重新改写了cm2接口,接口如下:
ZOOAPI void log_message(ZooLogLevel curLevel, int line, const char* funcName, const char* message);
为了实现重载,在这里把它改成弱符号
ZOOAPI void log_message(ZooLogLevel curLevel, int line, const char* funcName, const char* message) __attribute__((weak));
最后在使用的地方重载这个函数,这样程序在链接的时候,会把这个弱符号函数重定向到这个地方。
void log_message(ZooLogLevel curLevel, int line, const char* funcName, const char* message){ alog::Logger::getRootLogger() ->log(ZK_TO_ALOG_LEVEL[curlLevel],"",line,funcName,"%s".message); }
扫一扫订阅我的微信号:IT技术博客大学习
- 作者:霜天 来源: 搜索技术博客-淘宝
- 标签: 弱符号 强符号
- 发布时间:2013-04-07 13:11:07
- [70] IOS安全–浅谈关于IOS加固的几种方法
- [69] Twitter/微博客的学习摘要
- [64] 如何拿下简短的域名
- [63] Go Reflect 性能
- [63] android 开发入门
- [61] find命令的一点注意事项
- [59] 流程管理与用户研究
- [58] Oracle MTS模式下 进程地址与会话信
- [58] 读书笔记-壹百度:百度十年千倍的29条法则
- [58] 图书馆的世界纪录