Структура с тем же именем, разные определения: ошибка сегментации с -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 также должен быть запрещен в соответствии с правилом единого определения (которое есть на обоих языках). Нарушение, которое является неопределенным поведением, которое включает ошибки сегментации.