C / C ++:强制位域顺序和对齐

问题描述 投票:79回答:6

我读到结构中位字段的顺序是特定于平台的。如果我使用不同的编译器特定的打包选项,这样可以保证数据在写入时以正确的顺序存储吗?例如:

struct Message
{
  unsigned int version : 3;
  unsigned int type : 1;
  unsigned int id : 5;
  unsigned int data : 6;
} __attribute__ ((__packed__));

在具有GCC编译器的英特尔处理器上,字段在显示时显示在内存中。 Message.version是缓冲区中的前3位,然后是Message.type。如果我找到各种编译器的等效结构包装选项,这将是跨平台的吗?

c++ c bit-manipulation endianness bit
6个回答
94
投票

不,它不是完全便携的。结构的包装选项是扩展,并且它们本身不是完全可移植的。除此之外,C99§6.7.2.1第10段说:“单位内的位域分配顺序(从高阶到低阶或低阶到高阶)是实现定义的。”

例如,即使是单个编译器也可能根据目标平台的字节顺序不同地放置位字段。


41
投票

从编译器到编译器,位字段差异很大,抱歉。

对于GCC,大端机器首先布置了大端,而小端机器首先布置了小端。

K&R说“结构的相邻[bit-]字段成员在依赖于实现的方向上被打包到依赖于实现的存储单元中。当另一个字段后面的字段不适合时...它可以在单元之间分割,或者单元可以是宽度为0的未命名字段强制填充......“

因此,如果您需要与机器无关的二进制布局,则必须自己完成。

最后一个语句也适用于填充引起的非位域 - 但是所有编译器似乎都有一些强制结构字节打包的方法,正如我在GCC中发现的那样。


33
投票

应避免使用位域 - 即使在同一平台上,它们在编译器之间也不是很容易移植。来自C99标准6.7.2.1/10 - “结构和联合说明符”(C90标准中有类似的措辞):

实现可以分配足够大的任何可寻址存储单元来保持位域。如果剩余足够的空间,则紧跟在结构中的另一个位字段之后的位字段将被打包到相同单元的相邻位中。如果剩余的空间不足,则是否将不适合的位域放入下一个单元或重叠相邻单元是实现定义的。单元内的位域分配顺序(高阶到低阶或低阶到高阶)是实现定义的。未指定可寻址存储单元的对齐。

你不能保证位字段是否会“跨越”一个int边界,你不能指定一个位域是从int的低端开始还是从int的高端开始(这与处理器是否是独立的无关) big-endian或little-endian)。

喜欢位掩码。使用内联(甚至宏)来设置,清除和测试位。


9
投票

字节序正在谈论字节顺序而不是位顺序。如今,99%确定位订单是固定的。但是,使用位域时,应该计算字节序数。请参阅下面的示例。

#include <stdio.h>

typedef struct tagT{

    int a:4;
    int b:4;
    int c:8;
    int d:16;
}T;


int main()
{
    char data[]={0x12,0x34,0x56,0x78};
    T *t = (T*)data;
    printf("a =0x%x\n" ,t->a);
    printf("b =0x%x\n" ,t->b);
    printf("c =0x%x\n" ,t->c);
    printf("d =0x%x\n" ,t->d);

    return 0;
}

//- big endian :  mips24k-linux-gcc (GCC) 4.2.3 - big endian
a =0x1
b =0x2
c =0x34
d =0x5678
 1   2   3   4   5   6   7   8
\_/ \_/ \_____/ \_____________/
 a   b     c           d

// - little endian : gcc (Ubuntu 4.3.2-1ubuntu11) 4.3.2
a =0x2
b =0x1
c =0x34
d =0x7856
 7   8   5   6   3   4   1   2
\_____________/ \_____/ \_/ \_/
       d           c     b   a

6
投票

大多数时候,可能是,但不要把农场押在上面,因为如果你错了,你就会失去巨大的。

如果你真的需要相同的二进制信息,你需要创建带有位掩码的位域 - 例如你使用一个无符号的短(16位)作为Message,然后使像versionMask = 0xE000这样的东西代表三个最高位。

结构中的对齐存在类似的问题。例如,Sparc,PowerPC和680x0 CPU都是big-endian,Sparc和PowerPC编译器的常见默认设置是在4字节边界上对齐struct成员。但是,我用于680x0的一个编译器仅在2字节边界上对齐 - 并且没有更改对齐的选项!

因此对于某些结构,Sparc和PowerPC上的大小相同,但在680x0上较小,并且一些成员在结构中处于不同的内存偏移中。

这是我工作的一个项目的一个问题,因为在Sparc上运行的服务器进程会查询客户端并发现它是big-endian,并假设它只能在网络上喷出二进制结构并且客户端可以应对。这在PowerPC客户端上运行良好,并且在680x0客户端上大量崩溃。我没有编写代码,并且花了很长时间才找到问题。但是一旦我这么做就很容易解决。


-5
投票

当然,最好的答案是使用一个读取/写入位字段作为流的类。不能保证使用C位字段结构。更不用说在现实世界的编码中使用它被认为是不专业/懒惰/愚蠢的。

© www.soinside.com 2019 - 2024. All rights reserved.