Структура с тем же именем, разные определения: ошибка сегментации с -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

1

Решение

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

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

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

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

  • […]

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

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

7

Другие решения

От 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 не удовлетворяют этим требованиям, то поведение не определено.

1

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

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

0
По вопросам рекламы [email protected]