c++从一次静态库链接引发的double free聊起 
从一个例子说起 
在这个例子中,我们将创建一个可执行程序 A,它使用动态链接库 C,而 C 本身又依赖于另一个动态链接库 D。
库 C 和 D 都链接了库 E,其中库 E 包含一个使用 extern 声明和实现的 const std::string 全局变量。
文件内容 
文件的目录结构如下
Bash      /src
         main.cpp              //  可执行程序  A  的源文件
         libC.cpp              //  动态库  C  的源文件
         libD.cpp              //  动态库  D  的源文件
         libE.cpp              //  动态库  E  的源文件
         libE.h                //  动态库  E  的头文件
     /build
libE.h 
C++ #ifndef LIBE_H 
#define LIBE_H 
#include   <string> 
extern   const   std :: string   global_message ; 
void   printMessage (); 
#endif  // LIBE_H 
libE.cpp 
C++ #include   "libE.h" 
#include   <iostream> 
const   std :: string   global_message   =   "Hello from library E!" ; 
void   printMessage ()   { 
     std :: cout   <<   global_message   <<   " (Address: "   <<   & global_message   <<   ")"   <<   std :: endl ; 
} 
libD.cpp 
C++ #include   "libE.h" 
void   callPrintMessageFromD ()   { 
     printMessage (); 
} 
libC.cpp 
C++ #include   "libE.h" 
extern   void   callPrintMessageFromD (); 
void   callPrintMessageFromC ()   { 
     printMessage (); 
     callPrintMessageFromD (); 
} 
main.cpp 
C++ extern   void   callPrintMessageFromC (); 
int   main ()   { 
     callPrintMessageFromC (); 
     return   0 ; 
} 
采用cmake构建 
CMakeLists.txt文件如下:
C++ cmake_minimum_required ( VERSION   3.10 ) 
project ( a_demo ) 
add_library ( e   STATIC   libe . cpp ) 
target_compile_options ( e   PUBLIC   - fPIC ) 
add_library ( c   SHARED   libc . cpp ) 
target_link_libraries ( c   
e 
) 
add_library ( d   SHARED   libd . cpp ) 
target_link_libraries ( d 
e 
) 
add_executable ( main   
     main . cpp 
) 
target_link_libraries ( main 
     c 
     d 
) 
Bash   nm  -CD  libc.so  |   grep  global_message
  B  global_message[ abi:cxx11] 
  ~/a_demo/build#  nm  -CD  libd.so  |   grep  global_message
  B  global_message[ abi:cxx11] 
运行 
按照正常的逻辑,程序运行时,输出应该是下面这样的:
Bash   from  library  E!
  from  library  E!
然而,当你运行程序时,输出却是。报错了!!!你知道为什么会这样吗?你有想到这个结局吗!
Bash   from  library  E!  ( Address:  0x7fc90c3740a0) 
  from  library  E!  ( Address:  0x7fc90c3740a0) 
() :  double  free  detected  in   tcache  2 
  ( core  dumped) 
一起看一看 
global_message 是一个在库 E 中定义的 const std::string,并在库 C 和 D 中使用。
如果将 libE 编译为静态库而不是动态库,并且 libC 和 libD 都静态链接 libE,都包含一份global_message 实例,但是观察发现这两个实例的地址是一样的,析构两次,导致 global_message 的 double_free 问题。
检查符号表
Bash # 查看动态库中的 global_message 符号 
  -CD  libc.so  |   grep  global_message
  -CD  libd.so  |   grep  global_message
C++ void   printMessage ()   { 
     std :: cout   <<   global_message   <<   " (Address: "   <<   & global_message   <<   ")"   <<   std :: endl ; 
} 
为什么静态库会导致重复定义?
静态库的本质:一组 .o 文件的集合。链接时,链接器仅提取被引用的目标文件。
动态库链接静态库:每个动态库独立链接静态库时,会将所需的 .o 文件复制到自身,导致多份数据副本。
如何解决呢? 
有三种方法可以解决这个问题,下面一起来看一下:
将 libe 改为动态库 
编译 libe 为动态库:
Bash   -shared  -fPIC  libe.cpp  -o  libe.so
或者将上述CMakeLists.txt文件中的STATIC改为SHARED。让 libc.so 和 libd.so 链接动态库 libe.so,而非静态库 libe.a。效果:全局变量仅有一份实例,避免了重复析构。
使用单例模式重构全局变量 
修改 libe.h 和 libe.cpp:
C++ // libe.h 
#include   <string> 
const   std :: string &   get_global_message ();    // 返回引用而非 extern 变量 
void   printMessage (); 
// libe.cpp 
#include   "libe.h" 
#include   <iostream> 
const   std :: string &   get_global_message ()   { 
     static   const   std :: string   instance   =   "Hello from library E!" ;    // 局部静态变量 
     return   instance ; 
} 
void   printMessage ()   { 
     std :: cout   <<   get_global_message ()   <<   std :: endl ; 
} 
原理:利用局部静态变量的线程安全初始化(C++11 起),确保全局唯一实例。
采用inline 
C++ inline   const   std :: string   global_message   =   "Hello from library E!" ; 
添加inline也可以解决。添加inline后重新编译,查看符号表:
Bash   nm  -CD  libd.so  |   grep  global_message
  u  global_message[ abi:cxx11] 
0000000000004088   u  guard  variable  for   global_message[ abi:cxx11] 
guard variable在多编译单元场景下仅被初始化一次,避免重复构造或竞争条件
Inline const variables at namespace scope have external linkage by default (unlike the non-inline non-volatile const-qualified variables). (since C++17) 
https://en.cppreference.com/w/cpp/language/inline