Структура байтов структуры (поведение пакета #pragma) отличается в MSV C от clang / gcc - PullRequest
0 голосов
/ 11 марта 2020

Следующий код генерирует другое расположение в памяти на MSV C vs clang / g cc. Почему?

#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;
}

Выход на MSV C 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, соответствующий макету MSV C?

1 Ответ

2 голосов
/ 11 марта 2020

Использование #pragma pack вызывает поведение, определяемое реализацией.

Кроме того, foo не является классом стандартного макета из-за наличия нескольких подобъектов базового класса одного типа так что даже без pack его компоновка не подлежит никакому ABI.

Полагаться на компоновку класса нестандартной компоновки - это, честно говоря, ужасная идея, и, безусловно, есть лучший способ достичь любой цели здесь.

Вот несколько возможных подходов, которые не связаны с изменением кода (конечно, даже если какой-то из них пока работает, он может измениться в любое время):

  • Используйте clang или g ++ вместо MSV C в Windows.
  • Попробуйте передать флаги в MSV C, чтобы изменить поведение EBCO см. Здесь для записи , может быть, это может быть сделано, чтобы дать версию 0 1 5.
  • Отредактируйте исходный код g cc или clang, чтобы создать свой собственный компилятор и дать желаемую раскладку.

In g cc оптимизация пустого базового класса отключена классом, имеющим две базы одного и того же типа, поэтому вы можете включить его с изменением кода , как предлагается в комментариях к этому вопросу :

struct empty_struct {};
struct E2 {};

class derived_struct : public E2

(а остальная часть кода такая же, как в вашем примере). Это дает мне вывод 0 0 4 даже без прагмы. Мне не известны какие-либо флаги для g cc или clang, которые могли бы изменить поведение EBCO.

Основанием для этого правила является то, что в стандарте C ++, если два допустимых указателя одного типа имеют одинаковое значение тогда они должны указывать на один и тот же объект. Два пустых подобъекта являются разными объектами, поэтому для них должны существовать уникальные адреса. MSV C не соответствует этому.

В C ++ 20 есть атрибут [[no_unique_address]], который предположительно ослабляет это требование, однако я попробовал его в своей установке g ++ 9.2.0 и это не изменило макет. Не уверен, является ли это ошибкой или предполагаемым поведением, но в любом случае это не кажется решением проблемы.

...