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

时间:2021-05-06 09:50:47

标签: c struct alignment padding pragma

我有一些遗留代码(目标架构 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 指令并假设前三个字段要打包吗?

1 个答案:

答案 0 :(得分:2)

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 *,然后尝试取消引用该指针。如果是这样,该代码已损坏,应该修复。

相关问题