具有相同名称,不同定义的结构:使用-O2进行分段故障

时间:2017-01-05 09:03:17

标签: c++ struct segmentation-fault translation-unit

我在C ++程序中遇到分段错误,当两个C ++文件一起编译时,每个C ++文件都包含一个不同的结构定义(具有相同的名称)。

根据this question,我了解结构定义仅限于翻译单元(文件及其内容)。

但是,在编译时启用-O1或更多时,我遇到了崩溃。 以下最小代码再现了段错误。

代码有3个简短的C ++文件和2个标题:

// td_collision1.cc
#include <iostream>
#include <vector>
#include <cstdlib>
#include "td1.h"

struct Data
{
  long a;
  double m1;
  double m2;
};

void sz1(void) {
    std::cout << "Size of in collision1: " << sizeof(struct Data) << std::endl;
}

void collision1(void) {
    struct Data tmp;
    std::vector<struct Data> foo;
    for (int i=0; i<10; i++) {
        tmp.a = 1;
        tmp.m1 = 0;
        tmp.m2 = 0;
        foo.push_back(tmp);
    }
}
// td1.h
#include <iostream>

void collision1(void);
void sz1(void);

// td_collision2.cc
#include <iostream>
#include <vector>
#include <cstdlib>
#include "td2.h"

struct Data {
  long a;
  double m1; // note that there is one member less here
};

void sz2(void) {
    std::cout << "Size of in collision2: " << sizeof(struct Data) << std::endl;
}

void collision2(void) {
    struct Data tmp2;
    std::vector<struct Data> bar;
    for (int i=0; i<100; i++) {
        tmp2.a = 1;
        tmp2.m1 = 0;
        bar.push_back(tmp2); // errors occur here
    }
}
// td2.h
#include <iostream>

void collision2(void);
void sz2(void);

// td_main.cc
#include <iostream>
#include <cstdlib>
#include "td1.h"
#include "td2.h"

int main(void) {
    sz1();
    sz2();
    collision2();
}

使用带有-O0标志的GCC 6.3编译的代码运行正常,并且在valgrind下没有错误。 但是,使用-O1或O2运行它会导致以下输出:

Size of in collision1: 24
Size of in collision2: 16
==326== Invalid write of size 8
==326==    at 0x400F6C: construct<Data, const Data&> (new_allocator.h:120)
==326==    by 0x400F6C: construct<Data, const Data&> (alloc_traits.h:455)
==326==    by 0x400F6C: push_back (stl_vector.h:918)
==326==    by 0x400F6C: collision2() (td_collision2.cc:22)
==326==    by 0x400FE8: main (td_main.cc:10)
==326==  Address 0x5aba1f0 is 0 bytes after a block of size 96 alloc'd
==326==    at 0x4C2E1FC: operator new(unsigned long) (vg_replace_malloc.c:334)
==326==    by 0x400DE9: allocate (new_allocator.h:104)
==326==    by 0x400DE9: allocate (alloc_traits.h:416)
==326==    by 0x400DE9: _M_allocate (stl_vector.h:170)
==326==    by 0x400DE9: void std::vector<Data, std::allocator<Data> >::_M_emplace_back_aux<Data const&>(Data const&) (vector.tcc:412)
==326==    by 0x400F7E: push_back (stl_vector.h:924)
==326==    by 0x400F7E: collision2() (td_collision2.cc:22)
==326==    by 0x400FE8: main (td_main.cc:10)
==326== 
==326== Invalid write of size 8
==326==    at 0x400F69: construct<Data, const Data&> (new_allocator.h:120)
==326==    by 0x400F69: construct<Data, const Data&> (alloc_traits.h:455)
==326==    by 0x400F69: push_back (stl_vector.h:918)
==326==    by 0x400F69: collision2() (td_collision2.cc:22)
==326==    by 0x400FE8: main (td_main.cc:10)
==326==  Address 0x5aba1f8 is 8 bytes after a block of size 96 alloc'd
==326==    at 0x4C2E1FC: operator new(unsigned long) (vg_replace_malloc.c:334)
==326==    by 0x400DE9: allocate (new_allocator.h:104)
==326==    by 0x400DE9: allocate (alloc_traits.h:416)
==326==    by 0x400DE9: _M_allocate (stl_vector.h:170)
==326==    by 0x400DE9: void std::vector<Data, std::allocator<Data> >::_M_emplace_back_aux<Data const&>(Data const&) (vector.tcc:412)
==326==    by 0x400F7E: push_back (stl_vector.h:924)
==326==    by 0x400F7E: collision2() (td_collision2.cc:22)
==326==    by 0x400FE8: main (td_main.cc:10)
==326== 
==326== 
==326== HEAP SUMMARY:
==326==     in use at exit: 0 bytes in 0 blocks
==326==   total heap usage: 5 allocs, 5 frees, 73,896 bytes allocated
==326== 
==326== All heap blocks were freed -- no leaks are possible
==326== 
==326== For counts of detected and suppressed errors, rerun with: -v
==326== ERROR SUMMARY: 191 errors from 2 contexts (suppressed: 0 from 0)

当libc重新分配push_back()时,std::vector<struct Data> bar函数失败。 (在我的例子中,它的大小最初是4个项目,然后在循环中调用push_back()时,向量会进一步调整大小。) 当td_collision1.cc中的struct Data与td_collision2.cc中的collision1()大小相同时,程序不会崩溃。

因此,这两个结构定义之间似乎存在冲突。实际上,如果我重命名一个结构,那么这个bug显然会消失。 但是,如上所述,我认为这不可能发生。我误解了什么? 此外,如果我摆脱函数struct Data,则段错误消失(碰撞1中的{{1}}可能被编译器抛弃,因为未使用)

我的理解是这两个CC文件之间存在明显的分离,如果标题中没有结构,则不应该存在“串扰”。

编辑:添加缺少的td2.h

3 个答案:

答案 0 :(得分:7)

您链接的答案是C语言,C不是C ++。

在C ++中(引自key,请参阅标准en.cppreference),规则如下:

  

程序中可以有多个定义,只要每个定义出现在以下各项的不同翻译单元中:class type [...],只要满足以下所有条件:

     
      
  • 每个定义由相同的令牌序列组成(通常出现在同一个头文件中)

  •   
  • [...]

  •   
     

如果满足所有这些要求,程序就会表现得好像整个程序中只有一个定义。否则,行为未定义

您的两个定义明显违反了第一个条件,因此行为未定义。

答案 1 :(得分:1)

来自basic.def.odr,(我省略了...):

  

可以有多个类类型的定义(Clause [class]),.....如果在多个翻译单元中定义了这样一个名为D的实体,那么:

     
      
  • D的每个定义应由相同的令牌序列组成;和
  •   
  • ...
  •   
     

如果D是模板并且是在多个翻译单元中定义的,那么前面的要求既适用于模板定义中使用的模板封闭范围的名称([temp.nondep]),也适用于相关名称在实例化时([temp.dep])。如果D的定义满足所有这些要求,则行为就好像存在D的单个定义。如果D的定义不满足这些要求,则行为未定义。

在您的计划中,struct Datatd_collision1.cctd_collision2.cc的定义彼此不匹配,因此struct Data的定义不符合这些要求,然后行为未定义。

答案 2 :(得分:0)

嗯,您正在链接C答案,但您的问题是关于C ++。两种语言,两种标准,两种答案。

那就是说,根据一种定义规则(两种语言都有),我认为C答案应该是不允许的。违反该原因的是未定义行为,其中包括分段错误。