Минимальный пример обсуждаемых блоков кода также доступен в примере godbolt.
Один из регистров, TransferNoBytes отображается в структуру как битовое поле следующим образом.
Код: Выделить всё
#pragma pack(push, 1)
// More structs
struct TransferNoBytes {
union {
struct {
uint32_t tnb : 24;
uint32_t res : 8;
};
uint32_t raw;
};
};
// More structs
#pragma pack(pop)
Сборка:
Код: Выделить всё
write_tnb(TransferNoBytes volatile*, unsigned int):
movzx eax, sil
movzx edx, BYTE PTR [rdi]
mov BYTE PTR [rdi], al
mov eax, esi
shr rsi, 16
movzx edx, BYTE PTR [rdi+1]
movzx eax, ah
movzx esi, sil
mov BYTE PTR [rdi+1], al
movzx eax, BYTE PTR [rdi+2]
mov BYTE PTR [rdi+2], sil
ret
Сборка:
Код: Выделить всё
write_tnb(TransferNoBytes volatile*, unsigned int):
and esi, 16777215
mov eax, -16777216
and eax, dword ptr [rdi]
or eax, esi
mov dword ptr [rdi], eax
ret
Код: Выделить всё
YesКомпилятор
Код: Выделить всё
#pragma pack(1)Код: Выделить всё
#pragma pack()Код: Выделить всё
[[gnu::packed]]Код: Выделить всё
[[gnu::packed, gnu::aligned(1)]]Код: Выделить всё
#pragma pack(4)Код: Выделить всё
[[gnu::packed, gnu::aligned(4)]]GCC
Нет
Да
Нет
Нет
Да
Да
Clang
Да
Да
Да
Да
Да
Да
При копании в документации GCC единственным намеком на эту разницу, который я нашел, был этот gccint запись:
Если loc находится в памяти, его режим должен быть режимом однобайтового целого числа. Если loc находится в регистре, используемый режим определяется операндом шаблона insv или extv (см. Стандартные имена шаблонов для генерации) и обычно представляет собой целочисленный режим с полным словом, который используется по умолчанию, если ничего не указано.
Поэтому я бы сделал вывод, что GCC должен был обнаружить битовое поле в памяти, что имело бы смысл, учитывая затруднительное положение структуры, отображаемой в память.
Однако это по-прежнему не объясняет, почему это происходит только при явной упаковке структуры с помощью [[gnu::packed]] или #pragma Pack(1), но не с помощью #pragma Pack() или явного пакета и выравнивания по 4 байтам [[gnu::packed, gnu::aligned(4)]].
Кроме того, я проверил вывод pahole в приложении, скомпилированном в режиме отладки с -O0 -g, который не показал различий в макете, по крайней мере, из отладочной информации.
В примере используется #pragma package(1).
Код: Выделить всё
pahole --anon_include --nested_anon_include --show_only_data_members test-gcc.o
Код: Выделить всё
struct TransferNoBytes {
union {
struct {
uint32_t tnb:24; /* 0: 0 4 */
uint32_t res:8; /* 0:24 4 */
}; /* 0 4 */
uint32_t raw; /* 0 4 */
}; /* 0 4 */
/* size: 4, cachelines: 1, members: 1 */
/* last cacheline: 4 bytes */
};
Код: Выделить всё
struct TransferNoBytes {
union {
struct {
uint32_t tnb:24; /* 0: 0 4 */
uint32_t res:8; /* 0:24 4 */
} __attribute__((__packed__)) __attribute__((__aligned__(1))); /* 0 4 */
uint32_t raw; /* 0 4 */
} __attribute__((__aligned__(1))); /* 0 4 */
/* size: 4, cachelines: 1, members: 1 */
/* forced alignments: 1 */
/* last cacheline: 4 bytes */
} __attribute__((__packed__));
Если атрибуты помещаются в GCC вручную, он даже выдает предупреждение: выравнивание 1 для «TransferNoBytes» меньше 4 [-Wpacked-not-aligned], что дополнительно означает, что GCC не является доволен однобайтовым выравниванием, хотя по-прежнему производит ту же сборку.
Так что проверка отладочной информации также не помогла выявить виновника.
Наконец, в долгосрочной перспективе выравнивание следует переключить на 4, поскольку регистры 32-битные, и не существует особого случая, когда упаковка и выравнивание должны быть равны 1, т. е. не меньше и не больше, чем 32-битные регистры или элементы данных.
Мне все же хотелось бы выяснить, если возможно, что может быть причиной этого, помимо различий в реализации компилятора.
Подробнее здесь: https://stackoverflow.com/questions/797 ... -opposed-t
Мобильная версия