Расположение байтов структуры (поведение #pragma pack) отличается в MSVC от clang/gcc
Следующий код создает другой макет в памяти на MSVC и clang/gcc. Зачем?
#include <stdio.h>
#pragma pack(push,1)
struct empty_struct
{
};
class derived_struct : public empty_struct
{
int m_derivedMember;
};
class derived_struct_container : public empty_struct
{
public:
derived_struct m_ds;
};
class foo
{
public:
foo() {}
derived_struct_container m_dsc;
int m_foo_member;
};
#pragma pack(pop)
int main()
{
foo fb;
printf("pf->m_dsc offset: %ld\n", (char *)&fb.m_dsc - (char *)&fb);
printf("pf->m_dsc.m_ds offset: %ld\n", (char *)&(fb.m_dsc.m_ds) - (char *)&fb);
printf("pf->m_foo_member offset: %ld\n", (char *)&(fb.m_foo_member) - (char *)&fb);
return 0;
}
Вывод на MSVC x64:
fb.m_dsc offset: 0
fb.m_dsc.m_ds offset: 0
fb.m_foo_member offset: 4
Вывод на clang x64 под Linux:
fb.m_dsc offset: 0
fb.m_dsc.m_ds offset: 1
fb.m_foo_member offset: 5
Как мне заставить макет clang соответствовать макету MSVC?
1 ответ
Использование #pragma pack
вызывает поведение, определяемое реализацией.
Также, foo
не является классом стандартного макета из-за наличия нескольких подобъектов базового класса одного и того же типа, поэтому даже безpack
его макет не подлежит никакому ABI.
Честно говоря, полагаться на макет класса нестандартного макета - ужасная идея, и, безусловно, есть лучший способ достичь любой цели.
Вот несколько возможных подходов, не связанных с изменением кода (конечно, даже если какой-либо из них сейчас работает, он может измениться в любой момент):
- Используйте clang или g++ вместо MSVC в Windows.
- Попробуйте передать флаги в MSVC, чтобы изменить поведение EBCO, см. Здесь для записи, возможно, это можно сделать, чтобы дать версию 0 1 5.
- Отредактируйте исходный код gcc или clang, чтобы создать собственный компилятор и дать желаемый макет.
В gcc оптимизация пустого базового класса отключена классом, имеющим две базы одного типа, поэтому вы можете включить ее, изменив код, как предлагается в комментариях к этому вопросу:
struct empty_struct {};
struct E2 {};
class derived_struct : public E2
(а остальная часть кода такая же, как в вашем примере). Это дает мне0 0 4
вывод даже без пакета прагмы. Мне неизвестны какие-либо флаги для gcc или clang, которые бы изменили поведение EBCO.
Обоснование этого правила заключается в том, что в Стандартном C++, если два действительных указателя одного типа имеют одинаковое значение, они должны указывать на один и тот же объект. Два пустых подобъекта - это разные объекты, поэтому для них должны существовать уникальные адреса. MSVC в этом отношении не соответствует требованиям.
В C++20 есть атрибут [[no_unique_address]]
это якобы смягчает это требование, однако я попробовал его при установке g++ 9.2.0, и он не изменил макет. Не уверен, что это ошибка или предполагаемое поведение, но в любом случае это не кажется решением проблемы.