只有位字段的结构体的并集,sizeof函数的字节数加倍,C

mqxuamgl  于 2023-03-17  发布在  其他
关注(0)|答案(4)|浏览(114)

由于某种原因,我不能完全弄清楚我的联合只是结构包含位字段是设置两倍的字节是必要的任何单一的结构。

#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的调用都返回4,但据我所知,它们应该返回2。

mepcadol

mepcadol1#

这里有几个问题,首先,你的位域Brane使用的是4字节的unsigned int。
即使只使用了一半的位,也仍然使用了一个完整的32位宽的无符号整型。
第二,Cmp位域使用两种不同的字段类型,所以前3个字段使用32位unsigned int中的8位,然后使用unsigned char,因为它是完整的8位。由于数据对齐规则,该结构至少为6字节,但可能更多。
如果你想优化你的联合体的大小,使其只占用16位,你首先需要使用unsigned short,然后你需要总是使用相同的字段类型,以保持所有内容在相同的空间。
像这样的事情会完全优化你的工会:

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周围。

mxg2im7a

mxg2im7a2#

C 20186.7.2.111允许C实现选择用于位字段的容器的大小:
一个实现可以分配任何足够大的可寻址存储单元来容纳一个位域。如果有足够的空间剩余,则结构中紧接着另一个位域的位域应被打包到同一单元的相邻位中。如果没有足够的空间剩余,则不适合的位域是否被放入下一个单元或与相邻单元重叠由实现定义......
您正在使用的实现显然选择使用四字节单元,这可能也是实现中int的大小,表明这是实现的方便大小。

ui7jx7zq

ui7jx7zq3#

它没有规定代码的作用,如果没有特定的系统和编译器,就没有意义去推理它。位字段在标准中的规定太差了,以至于不能可靠地用于内存布局之类的事情。

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 */ 
};

该标准允许编译器为任何位字段类型选择一个“存储单元”,它可以是任何大小。
一种实现可以分配足够大以保持位字段的任何可寻址存储单元。
我们不知道的事:

  • unsigned int类型的位域有多大。32位可能有意义,但不能保证。
  • 位字段是否允许unsigned char
  • unsigned char类型的位域的大小。可以是8到32之间的任何大小。
  • 如果编译器选择了一个比预期的32位更小的存储单元,并且这些位不适合它,会发生什么?
  • 如果unsigned int位字段与unsigned char位字段相遇,会发生什么情况?
  • 如果在并集的结尾或开头(对齐)有填充。
  • 结构中各个存储单元的对齐方式。
  • MSB的位置。

我们可以知道的事情:

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

进一步的知识可以通过在头脑中有一个非常具体的系统和编译器来获得。
我们可以使用100%可移植和确定性的按位操作来代替位字段,无论如何都能产生相同的机器码。

vuktfyat

vuktfyat4#

了解内存结构填充/内存对齐。默认情况下,32位处理器从内存读取32位(4字节),因为速度更快。因此,在内存中,char + uint 32将写入4 + 4 = 8字节(1字节- char,3字节空间,4字节uint 32)。
把这些行加到程序的开始和结束处,结果就是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;
}

相关问题