【问题标题】:Incorrect struct size using bitfields使用位域的结构大小不正确
【发布时间】:2019-03-28 22:48:27
【问题描述】:

我最近不得不处理结构中的位域,并且遇到了我无法解释的行为。

下面的struct应该是9个字节,根据各自的sizeof。但是对主结构执行 sizeof 会产生 10 个字节。

以下程序产生“10; 1 1 2 1 2 1 1 =9”

int main(){
    struct{
        uint8_t doubleoscillator;

        struct{
            char monophonic : 1;
            char hold : 1;
            char padding : 6;
        } test;

        int16_t osc1_multisound; //int
        int8_t osc1_octave; // -2..1
        int16_t osc2_multisound; //int
        int8_t osc2_octave; // -2..1
        int8_t intervall;
    }osc;
    std::cout << sizeof(osc) << "; ";

    int a[7];
    a[0] = sizeof(osc.doubleoscillator);
    a[1] = sizeof(osc.test);
    a[2] = sizeof(osc.osc1_multisound);
    a[3] = sizeof(osc.osc1_octave);
    a[4] = sizeof(osc.osc2_multisound);
    a[5] = sizeof(osc.osc2_octave);
    a[6] = sizeof(osc.intervall);

    int total = 0;
    for(int i=0;i<7;i++){
        std::cout << a[i] << " ";
        total += a[i];
    }

    std::cout << " = " << total << std::endl;
    return 0;
}

为什么结构体内部变量的单个 sizeof() 和 osc 结构体的 sizeof() 产生的结果不同?

【问题讨论】:

  • 因为编译器正在填充以对齐成员。
  • 不应该已经对齐了吗?位域总和为 8 位
  • 具体来说,osc1_octaveosc2_multisound 之间有一个字节的填充。
  • 与您的位域无关。它与您在两个int16_t 之间添加了一个int8_t 有关
  • 未对齐的访问要么根本不起作用,要么效率低于对齐的访问,具体取决于处理器。典型行为是在 16 位边界上对齐 16 位值。有#pragmas 和/或attributes 可以改变这种行为。 Here's an example of the latter.

标签: c++ struct bit-fields


【解决方案1】:

主要出于性能原因,在结构的每个成员之前添加填充,以在结构的内存布局中对齐所述成员。因此ocs2_multisound 之前可能有一个填充字节,以确保它出现在结构中的多个字节处,该字节数是 2 的倍数(因为int16_t 的对齐方式为 2)。

此外,在完成所有这些操作之后,结构的总大小将被填充为其最严格对齐要求的倍数(即任何持有字段的最高对齐)。就是这样,例如所述类型数组的元素都将正确对齐。

可以在编译时通过alignof(T) 检查类型的对齐方式,其中T 是类型。

在这种情况下增加的大小是不可避免的,但减少填充字节的常见建议是按降序排列的顺序排列结构成员。这是因为下一项可以保证正确对齐而无需填充,因为前一个字段是相同对齐或更严格的对齐。因此,如果添加任何填充,它只会填充结构的总大小,而不是(浪费)字段之间的填充。

如今,对齐的原因主要是为了提高效率。在支持它的硬件上读取未对齐的内存块通常会慢两倍,因为它实际上是在读取它周围的两个内存块并提取它需要的内容。但是,如果您尝试读/写未对齐的内存,也有一些硬件根本无法工作。此类硬件通常会在此事件中触发硬件异常。

【讨论】:

  • 我希望我可以按照我的意愿对齐它们,但遗憾的是我不能。文件格式强制排序
  • @Elcan 文件格式?我希望你不是说你正在将二进制数据读入结构。这是未定义的行为,因为成员的位置没有标准化。假设哪些位域放置在哪里。
  • 我是认真的。它不是一个公共软件,要么就是这样,要么编写大约 1000 行代码来填充结构。我将我的 int16_t 移至 int8_t[2],它现在可以正常工作了,谢谢
  • @Elcan 每个字段应该只读取一次未格式化的二进制文件(并且您必须将位字段更改为普通整数类型并使用按位运算或为其编写包装器)。
  • 编写 1000 行代码来手动设置结构的每个成员是唯一安全/便携的方法,AFAIK。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-08-30
  • 1970-01-01
  • 1970-01-01
  • 2011-02-08
  • 1970-01-01
  • 1970-01-01
  • 2016-11-21
相关资源
最近更新 更多