libstdc++ 的 make_shared 布局在 gcc 4.x 和 gcc 6.x 之间是否发生了变化?

Did libstdc++'s layout for make_shared change between gcc 4.x and gcc 6.x?

考虑以下由三个文件组成的最小示例:

foo.h:

#pragma once
#include <memory>

struct X {
    uint64_t i = 0xdeadbeefdeadbeefULL;
};

void foo();

foo.cxx:

#include "foo.h"

void foo() {
    std::make_shared<X>();
}

main.cxx:

#include <memory>
#include "foo.h"

template std::shared_ptr<X> std::make_shared();

int main() {
    foo();
}

然后用不同版本的gcc编译两个翻译单元:

$ g++-4.8.2 -g -std=c++11 -O0 -c foo.cxx -o foo.o
$ g++-6.2.0 -std=c++11 -D_GLIBCXX_USE_CXX11_ABI=0 -O0 -g main.cxx foo.o -fsanitize=address -fno-omit-frame-pointer

请注意,我专门使用旧 ABI 进行编译。

运行 生成的可执行文件死了(如果两个 TU 都是用相同版本的 gcc 编译的,则不会死):

==33535==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x60300000eff8 at pc 0x000000401dcf bp 0x7fffffffd7f0 sp 0x7fffffffd7e8
WRITE of size 8 at 0x60300000eff8 thread T0
    #0 0x401dce in X::X() (.../a.out+0x401dce)
    #1 0x402758 in _ZN9__gnu_cxx13new_allocatorI1XE9constructIS1_IEEEvPT_DpOT0_ /.../gcc-4.8.2/include/c++/4.8.2/ext/new_allocator.h:120
    #2 0x402721 in _ZNSt16allocator_traitsISaI1XEE12_S_constructIS0_IEEENSt9enable_ifIXsrNS2_18__construct_helperIT_IDpT0_EEE5valueEvE4typeERS1_PS6_DpOS7_ /.../gcc-4.8.2/include/c++/4.8.2/bits/alloc_traits.h:254
    #3 0x4026fc in _ZNSt16allocator_traitsISaI1XEE9constructIS0_IEEEDTcl12_S_constructfp_fp0_spcl7forwardIT0_Efp1_EEERS1_PT_DpOS4_ /.../gcc-4.8.2/include/c++/4.8.2/bits/alloc_traits.h:393
    #4 0x4026a6 in std::_Sp_counted_ptr_inplace<X, std::allocator<X>, (__gnu_cxx::_Lock_policy)2>::_Sp_counted_ptr_inplace<>(std::allocator<X>) /.../gcc-4.8.2/include/c++/4.8.2/bits/shared_ptr_base.h:399
    #5 0x4025d4 in _ZN9__gnu_cxx13new_allocatorISt23_Sp_counted_ptr_inplaceI1XSaIS2_ELNS_12_Lock_policyE2EEE9constructIS5_IKS3_EEEvPT_DpOT0_ /.../gcc-4.8.2/include/c++/4.8.2/ext/new_allocator.h:120
    #6 0x402572 in _ZNSt16allocator_traitsISaISt23_Sp_counted_ptr_inplaceI1XSaIS1_ELN9__gnu_cxx12_Lock_policyE2EEEE12_S_constructIS5_IKS2_EEENSt9enable_ifIXsrNS7_18__construct_helperIT_IDpT0_EEE5valueEvE4typeERS6_PSC_DpOSD_ /.../gcc-4.8.2/include/c++/4.8.2/bits/alloc_traits.h:254
    #7 0x40253a in _ZNSt16allocator_traitsISaISt23_Sp_counted_ptr_inplaceI1XSaIS1_ELN9__gnu_cxx12_Lock_policyE2EEEE9constructIS5_IKS2_EEEDTcl12_S_constructfp_fp0_spcl7forwardIT0_Efp1_EEERS6_PT_DpOSA_ /.../gcc-4.8.2/include/c++/4.8.2/bits/alloc_traits.h:393
    #8 0x40249b in std::__shared_count<(__gnu_cxx::_Lock_policy)2>::__shared_count<X, std::allocator<X>>(std::_Sp_make_shared_tag, X*, std::allocator<X> const&) /.../gcc-4.8.2/include/c++/4.8.2/bits/shared_ptr_base.h:502
    #9 0x4023b1 in std::__shared_ptr<X, (__gnu_cxx::_Lock_policy)2>::__shared_ptr<std::allocator<X>>(std::_Sp_make_shared_tag, std::allocator<X> const&) /.../gcc-4.8.2/include/c++/4.8.2/bits/shared_ptr_base.h:957
    #10 0x402375 in std::shared_ptr<X>::shared_ptr<std::allocator<X>>(std::_Sp_make_shared_tag, std::allocator<X> const&) /.../gcc-4.8.2/include/c++/4.8.2/bits/shared_ptr.h:316
    #11 0x4022c4 in std::shared_ptr<X> std::allocate_shared<X, std::allocator<X>>(std::allocator<X> const&) /.../gcc-4.8.2/include/c++/4.8.2/bits/shared_ptr.h:598
    #12 0x402241 in _ZSt11make_sharedI1XIEESt10shared_ptrIT_EDpOT0_ /.../gcc-4.8.2/include/c++/4.8.2/bits/shared_ptr.h:614
    #13 0x4021cf in foo() /.../foo.cxx:4
    #14 0x400fd0 in main /.../main.cxx:7
    #15 0x7ffff6208b34 in __libc_start_main (/lib64/libc.so.6+0x21b34)
    #16 0x400ef8  (/.../a.out+0x400ef8)

对于 gcc 7 和 gcc 8 也是如此,但对于 gcc 5.4 则不然。这是 std::make_shared 特有的。这里发生了什么?我找不到有关 ABI 中断的任何信息,我也不明白什么样的更改会破坏此示例。

GCC 4.8 中的 C++11 支持仍处于实验阶段,因此使用 -std=c++11 编译的两个对象只有在都由 GCC 4 编译时才能链接在一起。8.x

有关更完整的解释,请参阅