Расположение байтов структуры (поведение #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, и он не изменил макет. Не уверен, что это ошибка или предполагаемое поведение, но в любом случае это не кажется решением проблемы.

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