首页 > 解决方案 > 在另一个变量的初始化程序中使用联合字段

问题描述

我正在为软件模块的单元测试建立记录。记录数据在发送到 UUT 之前被序列化。

记录包含位域,所以我想在编译时使用这些相同的位域构建序列化记录(以防止必须考虑小端和大端问题以及位域中的位去哪里)并使用联合来访问(序列化)数据。我必须计算记录的校验和,因此我需要将位域作为字节来执行此操作。

到目前为止,我的尝试是:

/* defines for 64 bit valid record */
#define REC3_ID        EEID_ARRAY_FIRST
#define REC3_SIZE      1
#define REC3_INDEX     248
#define REC3_SI0       MAKE_SIZE_INDEX0(REC3_SIZE,REC3_INDEX)
#define REC3_SI1       MAKE_SIZE_INDEX1(REC3_SIZE,REC3_INDEX)
#define REC3_VALUE0    0xf2
#define REC3_VALUE1    0x4f
#define REC3_VALUE2    0xb8
#define REC3_VALUE3    0xa0
#define REC3_DATA      \
    MAKE_CHKSUM7(REC3_ID,REC3_SI0,REC3_SI1,REC3_VALUE0,REC3_VALUE1,REC3_VALUE2,REC3_VALUE3),\
    REC3_ID,REC3_SI0,REC3_SI1,REC3_VALUE0,REC3_VALUE1,REC3_VALUE2,REC3_VALUE3

#define CHKSUM_SEED (0x2a)
#define MAKE_CHKSUM7(v0,v1,v2,v3,v4,v5,v6) (0x100-(((v0)+(v1)+(v2)+(v3)+(v4)+(v5)+(v6)+CHKSUM_SEED)%0x100))

typedef union
{
    uint8_t si[2];
    struct
    {
        uint16_t s: 6;
        uint16_t i: 10;
    } b;
} si_t;

MAKE_SIZE_INDEX0(size,index) ((si_t){.b.s=size,.b.i=index}).si[0]
MAKE_SIZE_INDEX1(size,index) ((si_t){.b.s=size,.b.i=index}).si[1]

static uint8_t rec3[] = {REC3_DATA};

问题在于宏MAKE_SIZE_INDEX0MAKE_SIZE_INDEX1. 我无法让它们编译(gcc 版本 5.4.0 20160609 (Ubuntu 5.4.0-6ubuntu1~16.04.11))

问题可以简化为:

uint8_t rec[] = {0x12, (si_t){.b.s=4,.b.i=8}.si[0], (si_t){.b.s=4,.b.i=8}.si[1], 0x34};

但这会导致错误:

error: initializer element is not constant

我知道我可以在运行时创建我的记录,但我想知道是否可以让预处理器处理它。

我的替代方案是:

#if defined (TGT_ARCHITECTURE_x86_64)
#define MAKE_SIZE_INDEX0(size,index) (((size)&0x3f)+(((index)<<6)&0xc0))
#define MAKE_SIZE_INDEX1(size,index) ((index>>2)&0xff)
#endif

但这取决于目标是小端还是大端以及它如何存储位域。

标签: gccunionsansi-c

解决方案


static uint8_t rec3[] = { (si_t){.b.s=4,.b.i=8}.si[0] };

具有静态存储持续时间的变量必须仅使用静态初始化程序进行初始化——它必须是一个常量表达式。有一个常量表达式中允许的列表- 在常量表达式中不允许对嵌入在复合文字中的数组使用数组下标运算符。和你做不到的基本一样static int a[] = {1, 2}; static int b = a[1];

在旁注中,该标准表示允许实现接受自定义形式的常量表达式。因此代码可能碰巧与不同的编译器甚至不同的 gcc 版本一起工作(与最新的 gcc 版本一样,您可以使用 const 限定变量初始化具有静态存储持续时间的变量,这是一个扩展)。

“初始化器元素不是常量”的编译器错误,因为用于初始化具有静态存储持续时间的变量的元素不是常量表达式。

使用位域来提取变量的位掩码取决于编译器,取决于编译器选项(gcc 存储布局),并且不应在可移植代码中使用。编译器可以自由地重新排序结构中的位域,并且可以自由地在位域成员之间添加填充。正如在 stackoverflow 上多次宣传的那样,使用位掩码——它们每次都有效。


推荐阅读