Структура с тем же именем, разные определения: ошибка сегментации с -O2

Я столкнулся с ошибкой сегментации в программе на C++, когда два скомпилированных вместе файла C++ содержат разные определения структуры (с одинаковыми именами).

В соответствии с этим вопросом, я понимаю, что определения структуры ограничены единицей перевода (файл и его включения).

Тем не менее, я получаю сбой при включении -O1 или более во время компиляции. Следующий минимальный код воспроизводит segfault.

Код находится в 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();
}

Этот код, скомпилированный с GCC 6.3 с флагом -O0, работает нормально и без ошибок в 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)

push_back() функция не работает, когда libc перераспределяет std::vector<struct Data> bar, (в моем случае его размер изначально равен 4 элементам, а вектор дополнительно изменяется при вызове push_back() в цикле.) Когда struct Data в td_collision1.cc имеет тот же размер, что и в td_collision2.cc, программа не падает.

Таким образом, кажется, что существует конфликт между этими двумя определениями структуры. Действительно, если я переименую одну структуру, ошибка, очевидно, исчезнет. Но, как уже упоминалось выше, я думал, что этого не может произойти. Что я не так понял? Кроме того, если я избавлюсь от функции collision1()Сигфо исчезает (struct Data в collision1, вероятно, исключен компилятором, потому что не используется)

Насколько я понимаю, существует четкое разделение между этими двумя файлами CC, и никакие "перекрестные помехи" не должны быть возможны, если структуры не присутствуют в заголовке.

Изменить: добавить отсутствующий td2.h

3 ответа

Решение

Ответ, который вы указали, относится к языку C, а C - это не C++.

В C++ (цитата из en.cppreference, см . Ответ Danh для стандарта), правило следующее:

В программе может быть несколько определений, если каждое определение отображается в разных единицах перевода, каждого из следующего: тип класса [...], если выполняется все следующее:

  • каждое определение состоит из одной и той же последовательности токенов (как правило, появляется в том же заголовочном файле)

  • [...]

Если все эти требования выполнены, программа ведет себя так, как будто во всей программе есть только одно определение. В противном случае поведение не определено.

Ваши два определения явно нарушают первое условие, поэтому поведение не определено.

Из basic.def.odr, (... опущен мной):

Может быть более одного определения типа класса (Clause [class]), ..... Если такая сущность с именем D определена более чем в одной единице перевода, то:

  • каждое определение D должно состоять из одинаковой последовательности токенов; а также
  • ...

Если D является шаблоном и определен более чем в одной единице перевода, то предыдущие требования должны применяться как к именам из области действия шаблона, используемой в определении шаблона ([temp.nondep]), так и к зависимым именам в точке создания экземпляров ([temp.dep]). Если определения D удовлетворяют всем этим требованиям, то поведение такое, как если бы было одно определение D. Если определения D не удовлетворяют этим требованиям, то поведение не определено.

В вашей программе определение struct Data в td_collision1.cc И в td_collision2.cc не совпадают друг с другом, следовательно, определения struct Data не удовлетворяют этим требованиям, то поведение не определено.

Ну, вы связываете ответ C, но ваш вопрос касается C++. Два языка, два стандарта, два ответа.

Тем не менее, я считаю, что ответ C также должен быть запрещен в соответствии с правилом единого определения (которое есть на обоих языках). Нарушение, которое является неопределенным поведением, которое включает ошибки сегментации.

Другие вопросы по тегам