Определение выравнивания структур C / C ++ по отношению к его членам - PullRequest
25 голосов
/ 13 декабря 2008

Можно ли найти выравнивание типа конструкции, если известно выравнивание элементов конструкции?

Например. для:

struct S
{
 a_t a;
 b_t b;
 c_t c[];
};

это выравнивание S = max (alignment_of (a), alignment_of (b), alignment_of (c))?

Выполняя поиск в Интернете, я обнаружил, что «для структурированных типов наибольшее требование выравнивания любого из его элементов определяет выравнивание структуры» (в Что каждый программист должен знать о памяти ), но я не смог найти что-нибудь похожее в стандарте (точнее, последний проект).


Отредактировано: Большое спасибо за все ответы, особенно Роберту Гэмблу, который дал действительно хороший ответ на первоначальный вопрос, и другим, кто внес вклад.

Короче говоря:

Чтобы обеспечить требования к выравниванию элементов конструкции, выравнивание конструкции должно быть как минимум таким же строгим, как выравнивание ее самого строгого элемента.

Что касается определения выравнивания структуры, было представлено несколько вариантов, и с небольшим исследованием это то, что я нашел:

  • c ++ std :: tr1 :: alignment_of
    • пока не стандартно, но близко (технический отчет 1), должно быть в C ++ 0x
    • в последнем черновике присутствуют следующие ограничения: Предварительное условие: T должен быть полным типом, ссылочным типом или массивом неизвестная граница, но не должна быть типом функции или (возможно, cv-квалифицированный) void.
      • это означает, что мой представленный вариант использования с гибким массивом C99 не будет работать (это не удивительно, поскольку гибкие массивы не являются стандартным c ++)
    • в последнем черновом варианте c ++ он определен в терминах нового ключевого слова - alignas (это имеет такое же полное требование к типу)
    • по моему мнению, если стандарт c ++ когда-либо будет поддерживать гибкие массивы C99, требование может быть ослаблено (выравнивание структуры с гибким массивом не должно изменяться в зависимости от количества элементов массива)
  • c ++ boost :: alignment_of
    • в основном замена tr1
    • , кажется, специализируется на void и возвращает 0 в этом случае (это запрещено в проекте c ++)
    • Примечание от разработчиков: строго говоря, вы должны полагаться только на значение ALIGNOF (T), кратное истинному выравниванию T, хотя на практике оно вычисляет правильное значение во всех случаях, о которых мы знаем.
    • Я не знаю, работает ли это с гибкими массивами, оно должно (может не работать вообще, это разрешает встроенную функцию компилятора на моей платформе, поэтому я не знаю, как оно будет вести себя в общем случае)
  • Эндрю Топ представил простое шаблонное решение для расчета выравнивания в ответах
    • кажется, что это очень близко к тому, что делает повышение (повышение будет дополнительно возвращать размер объекта в качестве выравнивания, если оно меньше расчетного выравнивания, насколько я могу видеть), так что, вероятно, применимо то же самое замечание
    • это работает с гибкими массивами
  • используйте Windbg.exe, чтобы узнать выравнивание символа
    • не время компиляции, не зависит от компилятора,
  • использование offsetof для анонимной структуры, содержащей тип
    • см. Ответы, ненадежные, не переносимые с C ++ non-POD
  • встроенные функции компилятора, например. MSVC __alignof
    • работает с гибкими массивами
    • ключевое слово alignof в последней версии c ++

Если мы хотим использовать «стандартное» решение, мы ограничены std :: tr1 :: alignment_of, но это не сработает, если вы смешаете свой код c ++ с гибкими массивами c99.

На мой взгляд, есть только 1 решение - используйте старый взлом структуры:

struct S
{
 a_t a;
 b_t b;
 c_t c[1]; // "has" more than 1 member, strictly speaking this is undefined behavior in both c and c++ when used this way
};

Расхождения в стандартах c и c ++ и их растущие различия неутешительны в этом случае (и во всех других случаях).


Еще один интересный вопрос (если мы не можем выяснить выравнивание структуры переносимым способом), каково наиболее строгое требование выравнивания из возможных. Я смог найти пару решений:

  • boost (внутренне) использует объединение различных типов и использует для него boost :: alignment_of
  • последний черновой вариант c ++ содержит std :: align_storage
    • Значение default-alignment должно быть самым строгим требованием выравнивания для любого типа объекта C ++, размер которого не превышает Len
      • поэтому std::alignment_of< std::aligned_storage<BigEnoughNumber>>::value должен дать нам максимальное выравнивание
      • только черновик, еще не стандарт (если вообще), tr1::aligned_storage не имеет этого свойства

Любые мысли по этому поводу также будут оценены.

Я временно снял флажок с принятым ответом, чтобы получить больше информации о новых подвопросах и ввести их в него

Ответы [ 10 ]

26 голосов
/ 13 декабря 2008

Здесь есть два тесно связанных понятия:

  1. Выравнивание, требуемое процессором для доступа к определенному объекту
  2. Выравнивание, которое фактически использует компилятор для помещения объектов в память

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

  • Структурам разрешено иметь отступы между членами (и в конце)
  • Массивам не разрешено иметь отступ между своими элементами
  • Вы можете создать массив любого типа структуры

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

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

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

Также нет переносимого способа (по крайней мере, в C), чтобы определить, как компилятор будет на самом деле выравнивать объект, хотя у многих компиляторов есть опции для обеспечения некоторого уровня контроля над выравниванием.

13 голосов
/ 13 декабря 2008

Я написал этот код черты типа для определения выравнивания любого типа (на основе уже обсужденных правил компилятора). Вы можете найти это полезным:

template <class T>
class Traits
{
public:
    struct AlignmentFinder
    {
        char a; 
        T b;
    };

    enum {AlignmentOf = sizeof(AlignmentFinder) - sizeof(T)};
};

Итак, теперь вы можете пойти:

std::cout << "The alignment of structure S is: " << Traits<S>::AlignmentOf << std::endl;
6 голосов
/ 13 декабря 2008

Следующий макрос вернет требование выравнивания любого заданного типа (даже если это структура):

#define TYPE_ALIGNMENT( t ) offsetof( struct { char x; t test; }, test )

Примечание: я, вероятно, заимствовал эту идею из заголовка Microsoft в некоторый момент назад в моем прошлом ...


Редактировать: как указывает Роберт Гэмбл в комментариях, этот макрос не гарантированно работает. На самом деле, это, конечно, не будет работать очень хорошо, если компилятор настроен для упаковки элементов в структурах. Поэтому, если вы решите использовать его, используйте его с осторожностью.

Некоторые компиляторы имеют расширение, позволяющее получить выравнивание типа (например, начиная с VS2002, MSVC имеет встроенную __alignof()). Их следует использовать, когда они доступны.

3 голосов
/ 13 декабря 2008

Как уже упоминалось, его реализация зависит. Visual Studio 2005 использует 8 байтов в качестве выравнивания структуры по умолчанию. Внутренне элементы выровнены по размеру - у поплавка выравнивание 4 байта, двойное использование 8 и т. Д.

Вы можете переопределить поведение с #pragma pack. GCC (и большинство компиляторов) имеют похожие параметры компилятора или прагмы.

3 голосов
/ 13 декабря 2008

Можно предположить выравнивание структуры, если вы знаете больше деталей об используемых параметрах компилятора. Например, #pragma pack (1) заставит выравнивание на уровне байтов для некоторых компиляторов.

Примечание: я знаю, что вопрос был о выравнивании, но дополнительным вопросом является заполнение. Для встроенного программирования, двоичных данных и т. Д. В общем, не принимайте во внимание выравнивание структуры, если это возможно. Скорее используйте явное заполнение, если необходимо в структурах. У меня были случаи, когда было невозможно дублировать точное выравнивание, используемое в одном компиляторе, на компилятор на другой платформе без добавления элементов заполнения. Это было связано с выравниванием структур внутри структур, поэтому добавление дополнительных элементов исправило это.

2 голосов
/ 13 декабря 2008

Если вы хотите выяснить это для конкретного случая в Windows, откройте windbg:

Windbg.exe -z \path\to\somemodule.dll -y \path\to\symbols

Затем запустите:

dt somemodule!CSomeType
1 голос
/ 20 октября 2012

Изменено с Блог Питера Джута

Выравнивание структуры C основано на собственном типе наибольшего размера в структуре, по крайней мере, обычно (исключение составляет что-то вроде использования 64-разрядного целого числа на win32, где требуется только 32-разрядное выравнивание).

Если у вас есть только символы и массивы символов, как только вы добавите int, этот int будет начинаться с 4-байтовой границы (с возможным скрытым заполнением перед элементом int). Кроме того, если структура не кратна sizeof (int), в конце будет добавлено скрытое заполнение. То же самое для коротких и 64-битных типов.

Пример:

struct blah1 {
    char x ;
    char y[2] ;
};

sizeof (blah1) == 3

struct blah1plusShort {
    char x ;
    char y[2] ;
    // <<< hidden one byte inserted by the compiler here
    // <<< z will start on a 2 byte boundary (if beginning of struct is aligned).
    short z ;
    char w ;
    // <<< hidden one byte tail pad inserted by the compiler.
    // <<< the total struct size is a multiple of the biggest element.
    // <<< This ensures alignment if used in an array.
};

sizeof (blah1plusShort) == 8

1 голос
/ 21 февраля 2009

Я согласен в основном с Полом Беттсом, Райаном и Даном. На самом деле, дело за разработчиком, вы можете либо оставить символику выравнивания по умолчанию, о которой заметил Роберт (объяснение Роберта - это просто поведение по умолчанию, а не принудительно или принудительно каким-либо образом), либо вы можете установить любое выравнивание по вашему желанию / Zp [# #].

Это означает, что если у вас есть typedef с числами с плавающей запятой, long double, uchar и т. Д., То различные наборы массивов включены. Затем есть другой тип, который имеет некоторые из этих элементов странной формы, и один байт, а затем другой нечетный элемент, он будет просто выровнен по любому предпочтению, определяемому файлом make / solution.

Как отмечалось ранее, используя команду dt windbg во время выполнения, вы можете узнать, как компилятор разместил структуру в памяти.

Вы также можете использовать любой инструмент для чтения pdb, например dia2dump, для извлечения этой информации из статически pdb.

1 голос
/ 13 декабря 2008

Я не думаю, что расположение памяти гарантируется каким-либо образом в любом стандарте C. Это очень зависит от поставщика и архитектора. Могут быть способы сделать это, которые работают в 90% случаев, но они не являются стандартными.

Я был бы очень рад оказаться неправым =)

0 голосов
/ 18 января 2017

Я прочитал этот ответ через 8 лет и чувствую, что принятый ответ от @Robert, как правило, правильный, но математически неправильный.

Чтобы обеспечить требования к выравниванию элементов конструкции, выравнивание структуры должно быть как минимум таким же строгим, как наименьшее общее кратное выравнивания ее элементов . Рассмотрим странный пример, где требования к выравниванию элементов равны 4 и 10; в этом случае выравнивание структуры - это LCM (4, 10), которое равно 20, а не 10. Конечно, странно видеть платформы с таким требованием выравнивания, которое не является степенью 2, и, таким образом, для всех практических случаев , выравнивание структуры равно максимальному выравниванию ее членов.

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

Обновление: Как указано @chqrlie в комментарии, стандарт C не допускает нечетных значений выравнивания. Однако этот ответ все еще доказывает, почему структурное выравнивание является максимальным из выравниваний его элементов, просто потому, что максимум оказывается наименьшим общим кратным, и, таким образом, элементы всегда выравниваются относительно общего множественного адреса.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...