global constructor

HQ在要求我们修改code style后,又让我检查并去掉"global constructor"。

 

第一次听说这玩意,就研究了一下。发现网上有人讨论的很精彩,就记下来。

“global constructors导致so的md5不一致”

 

“今天遇到一个奇怪的问题,同一个svn tag下的代码,co几份,每一份编译几次,scons出来的so的md5不一致,而且同一个目录下的so和obj文件编译几次的md5也是不一样的,但 同一个目录下的so和obj大小是一致的,不同目录的so和obj是不一定一样的,不一样的obj文件一般就是大8个字节。
对于md5不一致的so和obj,我nm | c++filt了一下,就是几个global constructors不一致。查了一些资料,还没完全弄清楚,就发到版上来问问。
不 一致的地方是:global constructors keyed to _ZN102_GLOBAL__N_build_release64_src_text_feature_OrderFieldMatchCompletenessExtractor.cpp_00000000_CA317E552_1E, 主要是“CA317E552”这一段不太一样,之前怀疑是编译时带了--export-dynamic的问题,去掉以后也不管用,不知道是怎么回事,还请 各位大侠帮忙看看。先行谢过。
ps:是同一份代码,同一台机器,同一个账户,甚至是同一个目录编译出来的so的md5都是不一样的。”

 

“感觉不应该这样的,对于确定的源代码,产生的binary一定是要一致的,不然不利于重现调试问题:假设一个build在客户机器上出现问题,在core file里找到相应的symbol, 但在公司开发环境只有source codes, binary 只能重新build. 如果binary每次都是产生不同的symbol name,那在原始build上的symbol name相当于没用了。杯具。”

 

“我也遇到过这种情况,环境是 gcc 4.1.2,贴一下当年的分析:

每次编译 md5sum 都变化的原因

每次编译 md5sum 都变化,帮同事查了一下,过程和结果如下:
1. 两次编译,保留所有的 .o
2. 比较发现有一个.o变了
3. objdump -d 反汇编两次的 .o 发现结果中有一个符号的名字每次都变,怀疑是C++全局对象动态初始化代码造成的。
4. 打开对应的 .cpp,没发现问题,但是该文件除了包含一些头文件外,实际为空文件。
5. 二分法逐渐去除包含的头文件,发现了第一个头文件依然会造成每次编译 md5sum 都变化
6. 打开该头文件,发现有 #include <iostream>,怀疑是它造成的
7. 写一个空的 a.cpp,#include <iostream>,每次编译都变化
8. 打开 /usr/include/c++/4.1.2/iostream,把他的内容抄到  a.cpp,每次编译都变化;
   去掉其中的 static ios_base::Init __ioinit; 不再变化,问题原因找到。
 
简单重现:
$ cat a.cpp
extern int foo();
static int n = foo();
//int foo(){}
最后一行的存在与否决定是否会变化:
 
$ cat a.cpp; g++ -c a.cpp && { md5sum a.o; objdump -d a.o; nm a.o; }
 
比较:
 
保留 foo 定义时生成的代码
0000002e <_GLOBAL__I__Z3foov>:
  2e:   55                      push   %ebp
  2f:   89 e5                   mov    %esp,%ebp
  31:   ba ff ff 00 00          mov    $0xffff,%edx
  36:   b8 01 00 00 00          mov    $0x1,%eax
  3b:   e8 c6 ff ff ff          call   6 <_Z41__static_initialization_and_destruction_0ii>
  40:   5d                      pop    %ebp
  41:   c3                      ret    

去调保留 foo 定义时生成的代码
00000028 <_GLOBAL__I_a.cpp_00000000_489C834E>:
  28:   55                      push   %ebp
  29:   89 e5                   mov    %esp,%ebp
  2b:   83 ec 08                sub    $0x8,%esp
  2e:   ba ff ff 00 00          mov    $0xffff,%edx
  33:   b8 01 00 00 00          mov    $0x1,%eax
  38:   e8 c3 ff ff ff          call   0 <_Z41__static_initialization_and_destruction_0ii>
  3d:   c9                      leave
  3e:   c3                      ret

原因:
gcc 静态全局对象动态初始化要生成额外的代码,当其所在的 translation unit 没有其他全局符号生成的时候,他需要生成更能保证唯一性的名字。
 
四种解决办法:
1. 再定义一个无用的全局符号,比较丑陋。
2. 如果再头文件中只需要声明不需要定义,改为 include iosfwd。
3. 既然该文件是空的,那就不编译不链接好了。
3. 对最终的可执行文件做 strip,缺点是出了问题没法调试了。”

 

“越来越有意思了,我在gcc的官网找到这么一个信息,可能有点关系:
http://gcc.gnu.org/bugzilla/show_bug.cgi?id=10591
其中有一句是这么说的“since at the moment we give
things in an anonymous namespace a random name to avoid exactly this kind of
problem.”
并提到相关bug在gcc 4.2.0 fix。我理解这是不是导致4.4.4表现ok的原因。
谢谢大家,我再看看RoachCock说的这种情况。”

 

“实验结果符合预期:
cat a.cpp; g++ -c a.cpp && { md5sum a.o; nm a.o|c++filt; }
extern int foo();
static int n = foo();

31e8db85138895c2a1548220200fa78b  a.o
000000000000002a t global constructors keyed to a.cpp_00000000_68145A2A
                 U foo()
0000000000000000 t __static_initialization_and_destruction_0(int, int)
                 U __gxx_personality_v0
0000000000000000 b n

cat a.cpp; g++ -c a.cpp && { md5sum a.o; nm a.o|c++filt; }
extern int foo();
static int n = foo();

321474f70890edd158b4068a0af0f570  a.o
000000000000002a t global constructors keyed to a.cpp_00000000_C9C90818
                 U foo()
0000000000000000 t __static_initialization_and_destruction_0(int, int)
                 U __gxx_personality_v0
0000000000000000 b n
==================华丽的分割线================
cat a.cpp; g++ -c a.cpp && { md5sum a.o; nm a.o|c++filt; }
extern int foo();
static int n = foo();
int aa;
71d646621029a7e832b7f4a76ed3a5b4  a.o
000000000000002a t global constructors keyed to aa
                 U foo()
0000000000000000 t __static_initialization_and_destruction_0(int, int)
                 U __gxx_personality_v0
0000000000000000 B aa
0000000000000004 b n

cat a.cpp; g++ -c a.cpp && { md5sum a.o; nm a.o|c++filt; }
extern int foo();
static int n = foo();
int aa;
71d646621029a7e832b7f4a76ed3a5b4  a.o
000000000000002a t global constructors keyed to aa
                 U foo()
0000000000000000 t __static_initialization_and_destruction_0(int, int)
                 U __gxx_personality_v0
0000000000000000 B aa
0000000000000004 b n

to坑王:比较md5sum是很多地方都在用的土办法,看2个版本是不是一致的。
build号自动加1只要不是体现在源代码级别上的就没太多问题啊,soname一般也不会变化那么快吧。当然你要是有更好的办法我更高兴啊,呵呵。”

 

“恩,不过他提出的4种方法却不太好改:
四种解决办法:
1. 再定义一个无用的全局符号,比较丑陋。
2. 如果再头文件中只需要声明不需要定义,改为 include iosfwd。
3. 既然该文件是空的,那就不编译不链接好了。
4. 对最终的可执行文件做 strip,缺点是出了问题没法调试了。
工 程复杂了,第1/2种都不好改,而我遇到的情况并不是说是空文件,第4种就更不好改了。pee你提出的-frandom-seed倒是也可以解 roachcock的情况,只是需要给不同的参数太麻烦,正在尝试在scons里边动态修改-frandom-seed的值,让每个文件编译的时候都有不 同的-frandom-seed值,比如文件名。”

 

posted @ 2015-07-27 10:39    阅读(1070)  评论(0编辑  收藏  举报