bitlvl
RU

Визуализатор раскладки структур C и битовых полей

Вставьте одно или несколько определений struct, union, enum или typedef. Каждая структура рисуется байт за байтом с её заполнением для выбранного ABI — ровно так, как её разместили бы GCC, Clang или MSVC. Затем вставьте hex-дамп, чтобы увидеть значения полей.

Декодировать hex-дамп в эту структуру
Порядок байтов

Результаты и сообщения этого инструмента выводятся на английском языке.

Почему в структурах есть дыры

У каждого скалярного типа есть выравнивание: в большинстве ABI 4-байтовый int должен лежать по адресу, кратному 4, а 8-байтовый double или указатель — по адресу, кратному 8. Компилятор вставляет заполнение перед членом, чтобы достичь его выравнивания, и в конце — чтобы в массивах структур каждый элемент оставался выровненным (выравнивание самой структуры равно выравниванию её самого требовательного члена). Поэтому struct { char c; int i; } занимает 8 байт, а не 5, а struct { char a; long b; char c; } — 24 байта, тогда как { long b; char a; char c; } — 16. Упорядочивание членов от наибольшего выравнивания к наименьшему убирает большую часть заполнения; именно это делает предложение переупорядочивания.

Чем различаются ABI

Типx86-64 SysVAArch64i386ARM32MSVC x64
long88444
pointer88448
double8, выравнивание 88, выравнивание 88, выравнивание 48, выравнивание 88, выравнивание 8
long longвыравнивание 8выравнивание 8выравнивание 4выравнивание 8выравнивание 8
long double16, выравнивание 1616, выравнивание 1612, выравнивание 488
wchar_t44442
правила битовых полейItanium: поле размещается со следующего бита, если только оно не пересекает окно в sizeof(type) байт; безымянные поля не влияют на выравнивание (x86) или влияют (ARM)каждая серия полей одного размера получает собственную единицу хранения; смена типа начинает новую единицу

Один и тот же исходный код может поэтому давать разную двоичную раскладку на разных целевых платформах, и именно поэтому структуры, уходящие в сеть или в файлы, должны использовать типы фиксированной ширины, явные члены-заполнители и static_assert(sizeof(struct x) == 24).

Битовые поля

Соседние битовые поля упаковываются в единицу хранения своего объявленного типа. В GCC и Clang поле размещается со следующего свободного бита, если только оно не пересекает окно в sizeof(type) байт — тогда оно переносится на следующую границу выравнивания; поле нулевой ширины (int :0) заставляет следующее поле начать новую единицу. MSVC выделяет новую единицу при каждой смене размера типа, поэтому int a:8; char b:8; занимает 5 байт в MSVC и 2 в GCC. Порядок битов внутри единицы следует порядку байтов целевой платформы (младшие биты первыми в little-endian), поэтому битовые поля — плохой способ описывать сетевые форматы.

packed, aligned и #pragma pack

__attribute__((packed)) убирает всё заполнение (члены могут оказаться невыровненными, что медленно на x86 и вызывает сбой в некоторых конфигурациях ARM). #pragma pack(n) ограничивает выравнивание членов n байтами. __attribute__((aligned(n))) и _Alignas(n) повышают выравнивание, обычно до 16 для SIMD или 64 для строки кэша. Всё это отображается в раскладке.

Вопросы

Насколько это точно?

Движок протестирован по clang -fdump-record-layouts для x86-64, i386, AArch64, ARM32 и MSVC x64 на наборе структур с битовыми полями, упаковкой, вложенными объединениями и полями нулевой ширины. GCC и Clang на этих ABI согласуются друг с другом. Экзотические случаи (типы с повышенным выравниванием в битовых полях, __int128 на 32 битах, векторные типы) проверяйте компилятором.

Как получить реальные значения от моего компилятора?

clang -Xclang -fdump-record-layouts -c file.c печатает каждую структуру; GCC: скомпилируйте с -g и запустите pahole на объектном файле либо используйте offsetof() и sizeof() в небольшой программе. Добавьте static_assert(offsetof(struct s, x) == 8), чтобы зафиксировать раскладку.

Пишет, что тип неизвестен.

Типы из системных заголовков (size_t, uint32_t, time_t и семейство DWORD в Windows) встроены; для всего остального нужно вставить его typedef выше структуры, которая его использует. Неизвестное имя считается 4-байтовым int и помечается.

Можно ли декодировать структуру из core dump или пакета?

Вставьте байты в раздел декодирования. Целые, float, char, указатели, массивы char и битовые поля показываются в выбранном порядке байтов; вложенные структуры перечисляются, но не раскрываются (выберите внутреннюю структуру, чтобы декодировать её отдельно).

Почему переупорядочивание не помогает моей структуре?

Если члены уже отсортированы по выравниванию или в структуре есть битовые поля либо анонимные объединения (они автоматически не переупорядочиваются), предложение скрыто. Заполнение в конце неизбежно, когда выравнивание самого большого члена не делит сумму размеров.