Как бороться с выравниванием данных?

Есть у меня 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;
};
struct messageHeader_s {
  uint8_t sync[0x02]  ;
  uint8_t id[1] ;
  uint16_t length;
}__attribute__ ((packed));

Result:

sizeof():
	messageHeader_s: 5 (need 5),

#pragma pack(1)

Да, с 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

#pragma это из Микросовтовского Си, портированное в GCC. Поэтому на разных платформах ее разные модификаторы надо применять с осторожностью.

Хотя #pragma pack(1) в данном случае эквивалентно

всю жизнь использовал #pragma pack(push, 1); никогда ничего не валилось. :slight_smile:
не забывать потом про #pragma pack(pop)