仅包含字符的结构是否无填充?

Are structs containing only chars paddingless?

我有一些遗留代码(目标体系结构 armv5tejl 和 armv7l)声明了如下所示的结构:

#pragma pack(push,1)
struct modbus_pkt_s
{
    uint8_t d_addr;
    uint8_t cmd;
    uint8_t payload[250];
    uint8_t payload_length;
    uint8_t pkt_length;
    modbus_erc_t erc;
};
#pragma pack(pop) 

其中 modbus_erc_t 是枚举:

typedef enum modbus_erc_e modbus_erc_t;

我还有一个函数可以计算 modbus 数据包的 CRC(它使用 d_addrcmdpayload) 并假定前三个结构字段已打包。

我想删除#pragma 指令,因为我遇到了未对齐的内存访问问题(导致 SIGBUS)。

考虑到前三个字段是无符号字符,我能否安全地删除这些#pragma 指令并假设它们被打包?

C 标准没有指定结构中的填充;允许编译器出于任何原因在成员之间插入填充。编译器通常没有理由在大小为一个字节的成员或此类数组之前插入填充,而 GCC 和 Clang 则不会。 (可以想象,在某些体系结构中对齐数组可能会有一点好处,使基地址更简单。这在这里没有实际意义。)

即使较早的成员不是单字节类型,您也可以通过删除结构周围的 #pragma 指令并用 __attribute__((__packed__)) 标记每个较早的元素来获得您请求的布局。例如,这段代码:

#include <stdio.h>


int main(void)
{
    typedef struct
    {
        char a __attribute__((__packed__));
        int  b __attribute__((__packed__));
        int  c;
    } foo;
    foo x = {0x1, 0x02030405, 0x06070809};
    unsigned char *p = (void *) &x;
    for (size_t i = 0; i < sizeof x; ++i)
        printf("%02hhx ", p[i]);
    printf("\n");
}

Apple Clang 11 显示第一个 int 已打包,但第二个正常对齐:

01 05 04 03 02 00 00 00 09 08 07 06

然而,虽然删除 pragma 指令仍会使您的早期成员保留在 GCC 和 Clang 中,但它可能会移动 erc 成员。结构被打包是有原因的,而且很可能不仅仅是因为 CRC 可以假设早期的成员被打包。这种打包通常用于通过某些通信通道传输的结构,仅更改通道一侧的结构将中断通信——发送方和接收方将使用不同的布局。

此外,您遇到未对齐的访问错误这一事实表明您的程序除了使用压缩结构外还做错了什么。打包结构时,编译器会生成适当的代码来访问其未对齐的成员。要出错,程序必须做一些其他事情,例如将打包的 modbus_erc_t 成员的地址分配给未标记为打包的 modbus_erc_t *,然后尝试取消引用该指针。如果是这样,该代码已损坏,应该修复。