global constructor

HQ在要求咱們修改code style後,又讓我檢查並去掉"global constructor"。php

 

第一次據說這玩意,就研究了一下。發現網上有人討論的很精彩,就記下來。ios

「global constructors致使so的md5不一致」c++

 

「今天遇到一個奇怪的問題,同一個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都是不同的。」dom

 

「感受不該該這樣的,對於肯定的源代碼,產生的binary必定是要一致的,否則不利於重現調試問題:假設一個build在客戶機器上出現問題,在core file裏找到相應的symbol, 但在公司開發環境只有source codes, binary 只能從新build. 若是binary每次都是產生不一樣的symbol name,那在原始build上的symbol name至關於沒用了。杯具。」svn

 

「我也遇到過這種狀況,環境是 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,缺點是出了問題無法調試了。」ui

 

「愈來愈有意思了,我在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說的這種狀況。」this

 

「實驗結果符合預期:
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通常也不會變化那麼快吧。固然你要是有更好的辦法我更高興啊,呵呵。」spa

 

「恩,不過他提出的4種方法卻不太好改:
四種解決辦法:
1. 再定義一個無用的全局符號,比較醜陋。
2. 若是再頭文件中只須要聲明不須要定義,改成 include iosfwd。
3. 既然該文件是空的,那就不編譯不連接好了。
4. 對最終的可執行文件作 strip,缺點是出了問題無法調試了。
工 程複雜了,第1/2種都很差改,而我遇到的狀況並非說是空文件,第4種就更很差改了。pee你提出的-frandom-seed卻是也能夠解 roachcock的狀況,只是須要給不一樣的參數太麻煩,正在嘗試在scons裏邊動態修改-frandom-seed的值,讓每一個文件編譯的時候都有不 同的-frandom-seed值,好比文件名。」3d

相關文章
相關標籤/搜索