只有位字段的结构联合,sizeof 函数加倍字节,C

Union of structs with only bit fields, sizeof function doubling bytes, C

出于某种原因,我不太明白我的仅包含位域的结构的并集设置的字节数是任何单个结构所需字节数的两倍。

#include <stdio.h>
#include <stdlib.h>

union instructionSet {
    struct Brane{
        unsigned int opcode: 4;
        unsigned int address: 12;
    } brane;
    struct Cmp{
        unsigned int opcode: 4;
        unsigned int blank: 1;
        unsigned int rsvd: 3;
        unsigned char letter: 8;
    } cmp;
    struct {
        unsigned int rsvd: 16;
    } reserved;
};

int main() {

    union instructionSet IR;// = (union instructionSet*)calloc(1, 2);

    printf("size of union %ld\n", sizeof(union instructionSet));
    printf("size of reserved %ld\n", sizeof(IR.reserved));
    printf("size of brane %ld\n", sizeof(IR.brane));
    printf("size of brane %ld\n", sizeof(IR.cmp));


    return 0;
}

所有对 sizeof return 4 的调用,但据我所知,它们应该是 returning 2.

阅读有关内存结构填充/内存对齐的信息。默认情况下,32 位处理器从内存中读取 32 位(4 字节),因为速度更快。因此在内存中 char + uint32 将写入 4 + 4 = 8 个字节(1byte - char,3bytes space,4bytes uint32)。

在程序的开头和结尾添加这些行,结果为 2。

#pragma pack(1)

#pragma unpack

这是对编译器说的方式:将内存对齐到 1 个字节(在 32 位处理器上默认为 4)。

PS:用不同的 #pragma pack 集试试这个例子:

struct s1 
{
    char a;
    char b;
    int c;
};

struct s2
{    
    char b;
    int c;
    char a;
};

int main() {
    printf("size of s1 %ld\n", sizeof(struct s1));
    printf("size of s2 %ld\n", sizeof(struct s2));

    return 0;
}

C 2018 6.7.2.1 11 允许 C 实现选择用于位域的容器大小:

An implementation may allocate any addressable storage unit large enough to hold a bit-field. If enough space remains, a bit-field that immediately follows another bit-field in a structure shall be packed into adjacent bits of the same unit. If insufficient space remains, whether a bit-field that does not fit is put into the next unit or overlaps adjacent units is implementation-defined.…

您使用的实现显然选择使用四字节单元。可能这也是实现中 int 的大小,表明它是实现的方便大小。

这里有几个问题,首先,您的位域 Brane 使用的是 4 字节的 unsigned int。

即使你只使用了一半的位,你仍然使用了一个完整的 32 位宽度的 unsigned int。

其次,您的 Cmp 位域使用两种不同的字段类型,因此您将 32 位 unsigned int 中的 8 位用于前 3 个字段,然后使用 unsigned char 作为完整的 8 位。 由于数据对齐规则,此结构至少为 6 个字节,但可能更多。

如果您想优化联合的大小以仅占用 16 位。您首先需要使用 unsigned short 然后您需要始终使用相同的字段类型以使所有内容保持相同 space.

像这样的东西会完全优化你的联盟:

union instructionSet {
    struct Brane{
        unsigned short opcode: 4;
        unsigned short address: 12;
    } brane;
    struct Cmp{
        unsigned short opcode: 4;
        unsigned short blank: 1;
        unsigned short rsvd: 3;
        unsigned short letter: 8;
    } cmp;
    struct {
        unsigned short rsvd: 16;
    } reserved;
};

这将使您周围的尺寸为 2。

未指定此代码的作用,如果不考虑特定的系统和编译器,对其进行推理是没有意义的。位域在标准中的规定太差,无法可靠地用于内存布局等事情。

union instructionSet {

    /* any number of padding bits may be inserted here */ 

    /* we don't know if what will follow is MSB or LSB */

    struct Brane{
        unsigned int opcode: 4; 
        unsigned int address: 12;
    } brane;
    struct Cmp{
        unsigned int opcode: 4;
        unsigned int blank: 1;
        unsigned int rsvd: 3;
        /* anything can happen here, "letter" can merge with the previous 
           storage unit or get placed in a new storage unit */
        unsigned char letter: 8; // unsigned char does not need to be supported
    } cmp;
    struct {
        unsigned int rsvd: 16;
    } reserved;

    /* any number of padding bits may be inserted here */ 
};

该标准允许编译器为任意大小的任意位域类型选择一个 "storage unit"。该标准简单地说明:

An implementation may allocate any addressable storage unit large enough to hold a bitfield.

我们不知道的事情:

  • unsigned int 类型的位域有多大。 32 位可能有意义但不能保证。
  • 如果位字段允许 unsigned char
  • unsigned char 类型的位域有多大。可以是从 8 到 32 的任何尺寸。
  • 如果编译器选择了比预期的 32 位更小的存储单元,并且这些位放不下,会发生什么情况。
  • 如果 unsigned int 位域遇到 unsigned char 位域会发生什么。
  • 如果并集的末尾或者开头(对齐)会有padding。
  • 结构中的各个存储单元如何对齐。
  • MSB 的位置。

我们可以知道的事情:

  • 我们在内存中创建了某种二进制 blob。
  • blob 的第一个字节位于内存中的最低有效地址。它可能包含数据或填充。

通过牢记一个非常具体的系统和编译器可以获得更多知识。


我们可以使用 100% 可移植和确定性的按位运算来代替位域,无论如何都会产生相同的机器代码。