首页 > 解决方案 > 用标准(便携式)C 方法替换紧凑结构的“__attribute__ ((packed))”

问题描述

将几个值转换为用于无线电传输的字节串必须避免不需要的字节。在 ARM 目标(32 位)上使用 GCC,我使用“属性((打包))”。该指令是基于 GCC 的(正如我在此处某处读到的),因此通常不可移植 - 我更喜欢。例子:

typedef struct __attribute__ ((packed)) {
    uint8_t     u8;  // (*)
    int16_t     i16; // (*)
    float       v;
    ...
    uint16_t    cs;  // (*)
}valset_t;           // valset_t is more often used 
valset_t   vs;

(*) 值将使用没有 ((packed)) 属性的 4 个字节,而不是根据需要使用一两个字节。用于传输的字节访问:

union{
    valset_t    vs;                     // value-set
    uint8_t     b[sizeof(valset_t)];    // byte array
}vs_u;

使用 vs_u.b[i] 。

C 中是否有更便携的解决方案来执行此任务?

标签: cgccalignmentpacked

解决方案


打包/填充不是标准化的,因此结构/联合严格来说是不可移植的。#pragma pack(1)更常见一些,并且被许多不同的编译器(包括 gcc)支持,但它仍然不是完全可移植的。

另请注意,存在填充是有原因的,这些具有非标准包装的结构在某些系统上可能是危险的或不必要的低效。

用于存储协议等的唯一 100% 可移植数据类型是unsigned char. 如果您为它们编写序列化器/反序列化器例程,则只能获得完全可移植的结构。这自然是以额外代码为代价的。反序列化示例:

valset_t data_to_valset (const unsigned char* data)
{
  valset_t result;
  result.something = ... ;
  ...
  return result;
}

在特定网络字节序的情况下,您可以在同一例程内将网络字节序转换为 CPU 字节序。

请注意,您必须像上面的函数示例一样输入它。您不能编写如下代码:

unsigned char data[n] = ... ;
valset_t* vs = (valset_t*)data; // BAD

这在多种不同的方面都是不好的:对齐、填充、严格的别名、字节序等等。

不过也可以反过来,使用 aunsigned char*逐字节检查或序列化结构。但是,这样做仍然不能解决填充字节或字节序的问题。


推荐阅读