Я попытался использовать структуру с битовыми полями разного размера. Общее количество используемых битов равно 64. Однако, когда я проверяю размер структуры, я получаю 11 вместо ожидаемого 8. Пытаясь разложить структуру, я увидел, что разница исходит из поля день. Если я упаковываю каждый бит, чтобы получить 8-битные пакеты, поле день упаковывается между «концом» месяц и «началом» час. Я не знаю, хороший ли это подход. Может ли кто-нибудь объяснить мне это?
typedef unsigned char uint8_t;
typedef struct frameHeader_t
{
uint8_t encryption : 2;
uint8_t frameVersion : 2;
uint8_t probeType : 4;
uint8_t dataType : 5;
uint8_t measurePeriod : 3;
uint8_t remontePerdiod : 4;
uint8_t nbrMeasure : 2;
uint8_t year : 7;
uint8_t month : 4;
uint8_t day : 5;
uint8_t hour : 5;
uint8_t minute : 6;
uint8_t second : 6;
uint8_t randomization : 5;
uint8_t status : 4;
}FrameHeader;
int main()
{
FrameHeader my_frameHeader;
printf("%d\n", sizeof(FrameHeader));
return 0;
}
Проблема вот в чем: "... ожидаемая 8". Нет ожидаемого размера битового поля. Их нельзя надежно использовать для отображения памяти.
В чистом C нет никаких гарантий относительно размещения структуры в памяти.
В C традиционный способ упаковки битовых полей состоит в том, чтобы упаковать их в целые числа, размер которых совпадает с объявленным целочисленным типом. Итак, в вашем примере они будут упакованы в 8-битные целые числа. Если битовое поле не помещалось, оно переходило к новому целому числу. Я считаю, что требования к упаковке с тех пор были ослаблены, но это все еще распространенная стратегия. Итак, в вашем случае упаковки (1) 2, 2, 4 (2) 5, 3 (3) 4, 2 (4) 7 (5) 4 (6) 5 (7) 5 (9) 6 (9) 6 (10) 5 (11) 4. Итак, вы получили 11 8-битных целых чисел. Если вы хотите улучшить упаковку, переключитесь на более крупный целочисленный тип, например. uint32_t.





Если вы запустите его через инструмент pahole, вы должны получить объяснение:
struct frameHeader_t {
uint8_t encryption:2; /* 0: 6 1 */
uint8_t frameVersion:2; /* 0: 4 1 */
uint8_t probeType:4; /* 0: 0 1 */
uint8_t dataType:5; /* 1: 3 1 */
uint8_t measurePeriod:3; /* 1: 0 1 */
uint8_t remontePerdiod:4; /* 2: 4 1 */
uint8_t nbrMeasure:2; /* 2: 2 1 */
/* XXX 2 bits hole, try to pack */
uint8_t year:7; /* 3: 1 1 */
/* XXX 1 bit hole, try to pack */
uint8_t month:4; /* 4: 4 1 */
/* XXX 4 bits hole, try to pack */
uint8_t day:5; /* 5: 3 1 */
/* XXX 3 bits hole, try to pack */
uint8_t hour:5; /* 6: 3 1 */
/* XXX 3 bits hole, try to pack */
uint8_t minute:6; /* 7: 2 1 */
/* XXX 2 bits hole, try to pack */
uint8_t second:6; /* 8: 2 1 */
/* XXX 2 bits hole, try to pack */
uint8_t randomization:5; /* 9: 3 1 */
/* XXX 3 bits hole, try to pack */
uint8_t status:4; /* 10: 4 1 */
/* size: 11, cachelines: 1, members: 15 */
/* bit holes: 8, sum bit holes: 20 bits */
/* bit_padding: 4 bits */
/* last cacheline: 11 bytes */
};
Вы используете uint8_t в качестве базового типа, поэтому поля дополняются группами по 8 бит.
Вы должны быть в состоянии полностью исключить заполнение, несколько более переносимо, чем с __attribute((packed)), используя unsigned long long/uint_least64_t (не менее 64 бит) в качестве базового типа битовых полей, но технически базовые типы не-int/non-unsigned-int для битовых полей поддержка не гарантируется, но вы можете использовать unsigned (не менее 16 бит, гарантированных стандартом C) после небольшой реорганизации битовых полей, например, в:
typedef struct frameHeader_t
{
//16
unsigned year : 7;
unsigned randomization : 5;
unsigned month : 4;
//16
unsigned second : 6;
unsigned minute : 6;
unsigned status : 4;
//16
unsigned hour : 5;
unsigned dataType : 5;
unsigned probeType : 4;
unsigned encryption : 2;
//16
unsigned day : 5;
unsigned remontePerdiod : 4;
unsigned measurePeriod : 3;
unsigned nbrMeasure : 2;
unsigned frameVersion : 2;
}FrameHeader;
//should be an unpadded 8 bytes as long as `unsigned` is 16,
//32, or 64 bits wide (I don't know of a platform where it isn't)
(Заполнение или его отсутствие не гарантируется, но я никогда не видел, чтобы реализация вставляла его, если в этом не было необходимости.)
проблема с вашим решением заключается в том, что оно не сохраняет порядок полей, что делает его бесполезным, когда полученные данные выбирают свой собственный порядок.
@P__J__ Я упоминаю, что использование unsigned long long/uint_least64_t должно исправить это без необходимости изменения порядка.
@P__J__ Для ввода-вывода я явно манипулирую битами из массива uin8_t, это должно быть наиболее переносимым подходом.
чтобы избежать магии упаковки, я обычно использую беззнаковые типы фиксированного размера с тем же размером
typedef struct frameHeader_t
{
uint64_t encryption : 2;
uint64_t frameVersion : 2;
uint64_t probeType : 4;
uint64_t dataType : 5;
uint64_t measurePeriod : 3;
uint64_t remontePerdiod : 4;
uint64_t nbrMeasure : 2;
uint64_t year : 7;
uint64_t month : 4;
uint64_t day : 5;
uint64_t hour : 5;
uint64_t minute : 6;
uint64_t second : 6;
uint64_t randomization : 5;
uint64_t status : 4;
}FrameHeader;
Вам, вероятно, понадобится
#pragma packили что-то подобное (в зависимости от вашего компилятора, точнее, препроцессора компилятора). Полеyear«приземляется» на два байта, поэтому, скорее всего, оно дополняется компилятором. Впоследствии некоторые поля, которые следуют за ним, также «приземляются» на два байта, поэтому применяется большее заполнение.