Есть у меня ESP32. И желание пакет данных описать структурой, красиво. А затем послать наружу - девайсу, которому красота не важна, а важно, чтобы биты стояли на нужных местах.
Но, проклятый alignment не даёт мне красоту навести. Как его укротить, гада такого?
Пример:
// 5 bytes
struct messageHeader_s {
uint8_t sync[0x02];
uint8_t id[1];
uint16_t length;
};
// 8 bytes
struct messagePayloadCommand01_s {
uint8_t port;
uint32_t speed;
struct {
uint16_t charLen : 2;
uint16_t parity : 3;
uint16_t nStopBits : 2;
uint16_t notUsed : 9;
} mode;
uint8_t mask;
};
// 3 byte
struct messageChecksum_s {
uint8_t checkA;
uint8_t checkB;
uint8_t checkC;
};
// 5+8+3 = 16 bytes
typedef struct {
struct messageHeader_s header;
struct messagePayloadCommand01_s payload;
struct messageChecksum_s checksum;
} messageCommand01_t;
void setup() {
// Fill the struct
messageCommand01_t messageCommand01 = {
.header = {
.sync = {0x01, 0x02},
.id = { 0x03 },
.length = 0x0504
},
.payload = {
.port = 0x06,
.speed = 0x0807,
.mode = {
.charLen = B11,
.parity = B111,
.nStopBits = B11,
.notUsed = 0x1FF
},
.mask = 0x09,
},
.checksum = {
.checkA = 0x0D,
.checkB = 0x0E,
.checkC = 0x0F
}
};
uint8_t* pMessage = (uint8_t*)&messageCommand01;
Serial.begin(115200);
printf("sizeof():\n\tmessageHeader_s: %d (need 5),\n\tmessagePayloadCommand01_s: %d (need 8),\n\tmessageChecksum_s: %d (need 3),\n\tmessageCommand01: %d (need 16)\n", sizeof(messageHeader_s), sizeof(messagePayloadCommand01_s), sizeof(messageChecksum_s), sizeof(messageCommand01));
printf("message dump:");
for (uint8_t i = 0x00; sizeof(messageCommand01) > i; i++) {
printf(" %02X", *pMessage );
pMessage++;
}
printf("\n");
}
void loop() {
}
Выхлоп (где в дампе нули - это компилятор навыравнивал):
sizeof():
messageHeader_s: 6 (need 5),
messagePayloadCommand01_s: 12 (need 8),
messageChecksum_s: 3 (need 3),
messageCommand01: 24 (need 16)
message dump: 01 02 03 00 04 05 00 00 06 00 00 00 07 08 00 00 FF FF 09 00 0D 0E 0F 00
union ?
union t_PumpTimePart {
struct {
unsigned char pumpTime[2];
unsigned char pumpPeriod;
};
unsigned long PartPumpTime;
};
union t_PumpDaysPart {
struct {
unsigned char pumpDays[4];
};
unsigned long PartPumpDays;
};
MMM
04.Июль.2024 13:02:39
3
struct messageHeader_s {
uint8_t sync[0x02] ;
uint8_t id[1] ;
uint16_t length;
}__attribute__ ((packed));
Result:
sizeof():
messageHeader_s: 5 (need 5),
Да, с packed всё красиво.
Главное - чтобы не валилось, как с #pragma pack(push,1). А то я как-то натыкался на такое - МК начинал внезапно в кору падать.
Итого:
// 5 bytes
struct messageHeader_s {
uint8_t sync[0x02];
uint8_t id;
uint16_t length;
} __attribute__((packed));
// 8 bytes
struct messagePayloadCommand01_s {
uint8_t port;
uint32_t speed;
struct {
uint16_t charLen : 2;
uint16_t parity : 3;
uint16_t nStopBits : 2;
uint16_t notUsed : 9;
} mode __attribute__((packed));
uint8_t mask;
} __attribute__((packed));
// 3 byte
struct messageChecksum_s {
uint8_t checkA;
uint8_t checkB;
uint8_t checkC;
} __attribute__((packed));
// 5+8+3 = 16 bytes
struct messageCommand01_s {
struct messageHeader_s header;
struct messagePayloadCommand01_s payload;
struct messageChecksum_s checksum;
} __attribute__((packed));
typedef messageCommand01_s messageCommand01_t;
void setup() {
// Fill the struct
// memset(&messageCommand01, 0x00, sizeof(messageCommand01));
messageCommand01_t messageCommand01 = {
.header = {
.sync = {0x01, 0x02},
.id = 0x03,
.length = 0x0504
},
.payload = {
.port = 0x06,
.speed = 0x0A090807,
.mode = {
.charLen = B11,
.parity = B111,
.nStopBits = B11,
.notUsed = 0x1FF
},
.mask = 0x09,
},
.checksum = {
.checkA = 0x0D,
.checkB = 0x0E,
.checkC = 0x0F
}
};
uint8_t* pMessage = (uint8_t*)&messageCommand01;
Serial.begin(115200);
printf("sizeof():\n\tmessageHeader_s: %d (need 5),\n\tmessagePayloadCommand01_s: %d (need 8),\n\tmessageChecksum_s: %d (need 3),\n\tmessageCommand01: %d (need 16)\n", sizeof(messageHeader_s), sizeof(messagePayloadCommand01_s), sizeof(messageChecksum_s), sizeof(messageCommand01));
printf("message dump:");
for (uint8_t i = 0x00; sizeof(messageCommand01) > i; i++) {
printf(" %02X", *pMessage );
pMessage++;
}
printf("\n");
}
void loop() {
}
Выхлоп:
sizeof():
messageHeader_s: 5 (need 5),
messagePayloadCommand01_s: 8 (need 8),
messageChecksum_s: 3 (need 3),
messageCommand01: 16 (need 16)
message dump: 01 02 03 04 05 06 07 08 09 0A FF FF 09 0D 0E 0F
MMM
04.Июль.2024 13:25:09
6
#pragma это из Микросовтовского Си, портированное в GCC. Поэтому на разных платформах ее разные модификаторы надо применять с осторожностью.
Хотя #pragma pack(1)
в данном случае эквивалентно
всю жизнь использовал #pragma pack(push, 1); никогда ничего не валилось.
не забывать потом про #pragma pack(pop)
валиться в корку может, если структура пакованная, а потом к ней идет обращение с указанием типа другого.
Ну, например, у вас в структуре есть, скажем, 6 байтовых полей, а вы берете, и читаете 4 из них, как unsigned long. Если не повезло, и первое поле не выровнено на 32 бита, может и грохнуться. Такое на многих процессорах встречается.
MMM
21.Июль.2024 06:50:15
9
Как читаете, простым приведением типа? Так потому оно и запрещено стандартом, что вызывает неопределенное поведение.
Пользуйтесь мемкопи
Поведение там определенное, просто у каждого процессора - свое. Кто-то допускает не выровненные обращения, кто-то генерирует exception. По поводу “запрещено стандартом” - не слышал такого.
Optron
22.Июль.2024 11:56:28
11
А я подозреваю, что тип bool дополняется одним байтом.
А bool об этом подозревает?
Optron
22.Июль.2024 12:02:01
13
А ему, скорее всего, пофигу.
А тебе, чтоб не подозревать никого, надо вручную писать
#pragma pack push(…) и
#pragma pack pop
тогда выравнивание ты будешь сам контролировать
(работает в GCC)