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值,比如文件名。”

时间: 2025-01-22 03:41:40

global constructor的相关文章

DSP中cmd文件(修改1)

DSP中cmd文件(修改1) 看TMS320C6000 Optimizing Compiler v7.6 User's Guide和TMS320C6000 Assembly Language Tools User's Guide ? CMD 的专业名称叫链接器配置文件,是存放链接器的配置信息的,其中比较关键的就是MEMORY和SECTIONS两个伪指令的使用. ? DSP工程文件夹中有两个cmd文件,其中一个为linker.cmd;另一个为c6455.cmd.c6455.cmd文件在工程中.工程

iOS -- warnings

Semantic Warnings Warning Message -WCFString-literal input conversion stopped due to an input byte that does not belong to the input codeset UTF-8 -WNSObject-attribute         __attribute ((NSObject)) may be put on a typedef only, attribute is ignore

IOS 警告 收集

Semantic Warnings Warning Message -WCFString-literal input conversion stopped due to an input byte that does not belong to the input codeset UTF-8 -WNSObject-attribute __attribute ((NSObject)) may be put on a typedef only, attribute is ignored -Wabst

DSP6455的cmd文件

DSP6455的cmd文件 CMD 的专业名称叫链接器配置文件,存放链接器的配置信息,DSP编译器的编译结果是未定位的,DSP也没有操作系统来定位执行代码,DSP系统的配置需求也不尽相同,因此需要定义代码存储位置.CMD分配ROM和RAM空间,告诉链接程序怎样计算地址和分配空间.所以不同的芯片就有不同大小的ROM和RAM,存放用户程序的地方也不尽相同.所以要根据芯片进行修改. DSP 芯片的片内存储器, 只要没有被 TI 占用, 用户都可以全权支配. TI 设计了 "CMD文件"这种与

WebkitIDL

概述 Web IDL 是一门语言,用来定义WebCore的接口如何绑定外部语言比如JavaScriptCore, ObjC, GObject,C++等等.要暴露WebCore的接口给这些外部语言的话,我们需要编写IDL文件例如XMLHttpRequest.idl, Element.idl等等.Webit编译的时候,这些IDL 文件会被解析.而后生成的代码会绑定到WebCore的实现,而且会自动生成JavaScriptCore,ObjC, GObject, C++接口. 本文描述的是编写IDL的实

IOS 警告 汇总

Semantic Warnings Warning Message -WCFString-literal input conversion stopped due to an input byte that does not belong to the input codeset UTF-8 -WNSObject-attribute __attribute ((NSObject)) may be put on a typedef only, attribute is ignored -Wabst

应用程序启动速度的优化

Mac OS/Android在下面Static Initializer Mozillaproject师通过优化Static Initializer(静态初始化,或全局建构函数, Global Constructor)和Binary布局来提升FireFox启动速度的文章.很有參考价值. 文章中以x86及x86-64平台为基础,以下加了Mac OS及Android上的binary布局. 什么是Static Initializer? 简而言之就是全局C++对象的初始化. 有人笑称一个C++程序的mai

iOS编程 手动忽略clang编译器警告

在iOS开发过程中, 我们可能会碰到一些系统方法弃用, weak.循环引用.不能运行之类的警告. 有代码洁癖的孩子们非常想消除他们, 今天就让我们来一次Fuck 警告.! 首先学会主要的语句 #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" 这里写出现警告的代码 #pragma clang diagnostic pop 这样就消除了方法弃用的警告. 同理,

JavaScript Patterns 5.5 Sandbox Pattern

Drawbacks of the namespacing pattern • Reliance on a single global variable to be the application’s global. In the namespacing pattern, there is no way to have two versions of the same application or library run on the same page, because they both ne