sizeof函数如何在有位字段和无位字段的结构上工作?(padding)

问题描述 投票:0回答:1

我在测试C++中带有位字段的结构的行为,但我遇到了一些困惑。我的操作系统是Windows 10 x64。

我使用的代码如下。

struct BitFieldTest
{
    bool flag1 : 1;
    bool flag2 : 1;
    bool flag3 : 1;
    bool flag4 : 1;
    unsigned char counter1 : 4;
    unsigned int counter2 : 4;
};

struct NormalFieldTest
{
    bool flag1;
    bool flag2;
    bool flag3;
    bool flag4;
    unsigned char counter1;
    unsigned int counter2;
};
struct NormalFieldTest2
{
    bool flag1;
    bool flag2;
    bool flag3;
    bool flag4;
    unsigned char counter1;
};

int main()
{
    std::cout << "Size of bool: " << sizeof(bool) << std::endl;
    std::cout << "Size of unsigned char: " << sizeof(unsigned char) << std::endl;
    std::cout << "Size of unsigned int: " << sizeof(unsigned int) << std::endl;

    std::cout << "Size of struct with bit field: " << sizeof(BitFieldTest) << std::endl;
    std::cout << "Size of struct without bit field: " << sizeof(NormalFieldTest) << std::endl;
    std::cout << "Size of struct without bit field: " << sizeof(NormalFieldTest2) << std::endl;
    return 0;
}

输出结果是:

Size of bool: 1
Size of unsigned char: 1
Size of unsigned int: 4
Size of struct with bit field: 8
Size of struct without bit field: 12
Size of struct without bit field 2: 5

我不明白为什么结构的大小是这样的。谁能解释一下或者分享一些关于这个主题的链接?

c++ alignment sizeof bit-fields
1个回答
4
投票

只要位字段的类型相同,位字段才会压缩在一起。所以一个位字段的类型是:

struct Test
{
    char Test1 : 1;
    char Test2 : 1;
    char Test3 : 1;
    char Test4 : 1;
    short Test5 : 4;
}

的位字段不会是一个字节长,而是四个。

这是因为两点--首先,位字段不会跨越类型边界。char中的位字段不能与short中的位字段混杂。

第二,为了对齐的目的,short 必须放在结构开始的两个字节边界上,所以编译器将其改为:

struct Test
{
    char Test1 : 1;
    char Test2 : 1;
    char Test3 : 1;
    char Test4 : 1;
    char BitPadding : 4;
    char AlignmentPadding;
    short Test5 : 4;
    short BitPadding2 : 12;
}

这样结构的长度就达到了4个字节。

现在我们依次查看每个结构。

struct BitFieldTest
{
    bool flag1 : 1;
    bool flag2 : 1;
    bool flag3 : 1;
    bool flag4 : 1;
    unsigned char counter1 : 4;
    unsigned int counter2 : 4;
};

在这里。flag1, flag2, flag3flag4 都具有相同的类型,并浓缩成一个四位值存储在一个字节中。counter1 是不同类型的,自然对齐方式为1,所以它移动到下一个字节边界。counter2 也是不同的类型,它的自然对齐方式是 4,所以它移动到下一个 4 字节的边界。如果我们再把各个部分的大小和中间的填充物相加,我们就会得到。

1个字节有4个标志,1个字节有4位计数器,2个字节的对齐填充,4个字节有4位计数器。

这样加起来有8个字节,和编译器报告的一样。

第二个结构没有位字段,但显示了对齐问题。

struct NormalFieldTest
{
    bool flag1;
    bool flag2;
    bool flag3;
    bool flag4;
    unsigned char counter1;
    unsigned int counter2;
};

这里,我们有:

1个字节表示布尔标志1,1个字节表示布尔标志2,1个字节表示布尔标志3,1个字节表示布尔标志4,1个字节表示计数器1,3个字节表示对齐填充,4个字节表示计数器2。

这样加起来就是12个,这也是编译器所报告的。

第三种结构与第二种结构大同小异,但缺少内部的对齐填充。

struct NormalFieldTest2
{
    bool flag1;
    bool flag2;
    bool flag3;
    bool flag4;
    unsigned char counter1;
};

这里,我们有:

1个字节代表布尔标志1,1个字节代表布尔标志2,1个字节代表布尔标志3,1个字节代表布尔标志4,1个字节代表计数器1。

如编译器所报告的那样,这样加起来有5个字节。

作为一个额外的说明,内部类型的自然对齐也会泄漏到结构本身。考虑以下结构。

struct TrailingAlignment
{
    int Field1;
    short Field2;
}

这个结构表面上看是6个字节,但编译时却会变成8个字节。这是因为这个结构可能会被用于一个数组中,如果它的长度是6个字节,那么数组中的每一个其他项都会包含一个错误对齐的 Field1,在不支持错位访问的系统中会造成很大的问题(比如ARM的某些版本)。为了避免这种情况,编译器在结构的末尾插入两个字节,以确保该结构在数组中的下一个实例将在四个字节的边界上对齐,假设第一个结构是正确对齐的。

这种情况不会发生在 NormalFieldTest2 因为自然排列为 boolchar 是一个字节,所以无论结构体在内存中的哪个位置,它都会被对齐。

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