C++中的编译器升级问题 _GLIBCXX_USE_CXX11_ABI

483 阅读2分钟

问题背景

最近负责编译器的升级,从gcc 4.8.5 版本升级到 gcc 6.3.0 版本,所以把所有依赖的第三方库都重新使用 gcc 6.3.0 版本编译器再编译一遍。但是过程并不顺利,在业务 project 中依赖第三方库,链接时出现链接失败的情况,提示很多函数 undefined。

问题现象

这里先通过一个实验来复现一下问题。

test_cpp 工程如下:

image-20230226203501490的副本.png

liba

a.h

#include <iostream>
#include <string>
#include <vector>
​
namespace liba {
​
void test_a(std::vector<std::string>& vec);
​
}

a.cpp

#include "a.h"
​
namespace liba {
​
void test_a(std::vector<std::string>& vec) {
  std::cout << "liba::test_a, vec.size=" << vec.size() << std::endl;
}
​
}

Makefile

SRC := $(wildcard *.cpp)
OBJ := $(patsubst %.cpp, %.o, $(SRC))
TARGET := liba.a
CXXFLAGS := -g -std=c++11 -D_GLIBCXX_USE_CXX11_ABI=1
​
$(TARGET): $(OBJ)
  ar rsc $@ $<$(OBJ): $(SRC)
  g++ -c $< -o $@ $(CXXFLAGS)clean:
  rm -f *.o *.a
​
.PHONY: clean

main

main.cpp

#include <iostream>
#include <string>
#include <vector>
#include "liba/a.h"int main() {
  std::vector<std::string> vec;
  liba::test_a(vec);
  return 0;
}

Makefile

SRC := $(wildcard *.cpp)
OBJ := $(patsubst %.cpp, %.o, $(SRC))
TARGET := main
CXXFLAGS := -g -std=c++11 -D_GLIBCXX_USE_CXX11_ABI=0
LDFLAGS := ../liba/liba.a
INCFLAGS := -I../
​
$(TARGET): $(OBJ)
  g++ -o $@ $< $(LDFLAGS)$(OBJ): $(SRC)
  g++ -o $@ -c $< $(CXXFLAGS) $(INCFLAGS)clean:
  rm -f main *.o
​
.PHONY: clean

编译 test_cpp

分别编译 liba 和 main:

[root@3b06228be59b liba]# make
g++ -c a.cpp -o a.o -g -std=c++11 -D_GLIBCXX_USE_CXX11_ABI=1
ar rsc liba.a a.o
[root@3b06228be59b liba]# cd ../main
[root@3b06228be59b main]# make
g++ -o main.o -c main.cpp -g -std=c++11 -D_GLIBCXX_USE_CXX11_ABI=0 -I../
g++ -o main main.o ../liba/liba.a
main.o: In function `main':
/test_cpp/main/main.cpp:8: undefined reference to `liba::test_a(std::vector<std::string, std::allocator<std::string> >&)'
collect2: error: ld returned 1 exit status
make: *** [main] Error 1

/test_cpp/main/main.cpp:8: undefined reference to `liba::test_a(std::vector<std::string, std::allocatorstd::string >&)'

提示这个函数没有定义:liba::test_a(std::vector<std::string, std::allocatorstd::string >&)。

使用 nm 命令查看 liba.a 是否对 liba::test_a 有定义。

[root@3b06228be59b main]# nm -C ../liba/liba.a | grep test_a
0000000000000089 t _GLOBAL__sub_I__ZN4liba6test_aERSt6vectorINSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEESaIS6_EE
0000000000000000 T liba::test_a(std::vector<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::allocator<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > >&)
[root@3b06228be59b main]#

我们看到 liba::test_a 已经定义。但是和以上提示 undefined 的函数原型不一样。是:

liba::test_a(std::vector<std::__ cxx11::basic_string<char, std::char_traits, std::allocator >, std::allocator<std::__ cxx11::basic_string<char, std::char_traits, std::allocator > > >&)

原因分析

根据问题的现象,我们知道在在 liba 和 main 看到的 liba::test_a 的函数原型不一样,分别是:

  • liba::test_a(std::vector<std::string, std::allocatorstd::string >&)
  • liba::test_a(std::vector<std::__ cxx11::basic_string<char, std::char_traits, std::allocator >, std::allocator<std::__cxx11::basic_string<char, std::char_traits, std::allocator > > >&)

所以,我们要先分析为什么会出现不一样的情况。

根本问题就在 std::__cxx11,所以去看看 gcc 6.3.0 编译器的源码吧。

从源码中发现 std::__cxx11 其实跟 _GLIBCXX_USE_CXX11_ABI 这个宏有关系,在 string 的实现中,当 _GLIBCXX_USE_CXX11_ABI = 1 时,会使用 std::__cxx11 相关的实现。

先看 main 的 Makefile:

SRC := $(wildcard *.cpp)
OBJ := $(patsubst %.cpp, %.o, $(SRC))
TARGET := main
CXXFLAGS := -g -std=c++11 -D_GLIBCXX_USE_CXX11_ABI=0
LDFLAGS := ../liba/liba.a
INCFLAGS := -I../
​
$(TARGET): $(OBJ)
  g++ -o $@ $< $(LDFLAGS)$(OBJ): $(SRC)
  g++ -o $@ -c $< $(CXXFLAGS) $(INCFLAGS)clean:
  rm -f main *.o
​
.PHONY: clean

再看 liba 的 Makefile:

SRC := $(wildcard *.cpp)
OBJ := $(patsubst %.cpp, %.o, $(SRC))
TARGET := liba.a
CXXFLAGS := -g -std=c++11 -D_GLIBCXX_USE_CXX11_ABI=1
​
$(TARGET): $(OBJ)
  ar rsc $@ $<$(OBJ): $(SRC)
  g++ -c $< -o $@ $(CXXFLAGS)clean:
  rm -f *.o *.a
​
.PHONY: clean

果然,问题出在 _GLIBCXX_USE_CXX11_ABI 这个宏。这个宏设置为 1 和 0 是使用了 std::string 的两种不同的代码实现。

这个问题在现实工作中的现象是,大家都使用了 blade-build 进行代码编译管理,BLADE_ROOT 配置文件大家都是拷贝来拷贝去的,BLADE_ROOT 中默认指定 -D_GLIBCXX_USE_CXX11_ABI=0,至于什么原因,已经无法追溯了,但是我猜可能就是为了解决在gcc 6.3.0 版本的编译中依赖了使用低版本编译器 gcc 4.8.5 编译出来的第三方依赖库的问题,因为 gcc 4.8.5 版本编译器默认 _GLIBCXX_USE_CXX11_ABI = 0,而 gcc 6.3.0 编译器默认 _GLIBCXX_USE_CXX11_ABI = 1。

再拓展一下 _GLIBCXX_USE_CXX11_ABI 出现的原因:

它主要是为了支持新的C++标准中要禁止 std::string 的 Copy-On-Write 行为和支持 std::list 中size() 的时间复杂度为 O(1)。

std::string 的 Copy-On-Write

先上个代码。

#include <cstdio>
#include <iostream>
#include <string>int main() {
  std::string str1 = "Hello, World.";
  std::string str2 = str1;
  printf("%p\n", str1.data());
  printf("%p\n", str2.data());
  return 0;  
}

使用 Copy-On-Write 的实现时,str1 和 str2 指向了同一个内存地址(即 "Hello, World." 的地址),只有对 str1 或 str2 进行修改时才会重新 copy 一块新的内存,在上面进行修改。

std::list 中size() 的时间复杂度为 O(1)

复杂度为 O(1) 实际上是增加了一个成员,用于记录 std::list 的 size。

参考文献

gcc.gnu.org/onlinedocs/…