char 和 int 上的 C struct 位字段之间的区别
different between C struct bitfields on char and on int
在 C 中使用位域时,我发现了与用于声明字段的实际类型有关的差异,这是我没有预料到的。
我没有找到任何明确的解释。现在,问题已确定,因此尽管没有明确的答复,但此 post 可能对面临相同问题的任何人都有用。
不过,如果有人能给出正式的解释,那就太好了。
下面的结构,占用内存2个字节。
struct {
char field0 : 1; // 1 bit - bit 0
char field1 : 2; // 2 bits - bits 2 down to 1
char field2 ; // 8 bits - bits 15 down to 8
} reg0;
这个占内存4个字节,请问为什么?
struct {
int field0 : 1; // 1 bit - bit 0
int field1 : 2; // 2 bits - bits 2 down to 1
char field2 ; // 8 bits - bits 15 down to 8
} reg1;
在这两种情况下,这些位在内存中的组织方式相同:字段 2 始终将位 15 降低为 8。
我试图找到一些关于这个主题的文献,但仍然无法得到明确的解释。
我能找到的两个最合适的链接是:
- http://www.catb.org/esr/structure-packing/
- http://www.msg.ucsf.edu/local/programs/IBM_Compilers/C:C++/html/language/ref/clrc03defbitf.htm
然而,none 真正解释了为什么第二个结构占用 4 个字节。实际上仔细阅读,我什至希望该结构占用 2 个字节。
在这两种情况下,
- field0占1位
- field1占用2位
- field2 占用 8 位,并对齐到第一个可用字节地址
因此,在这两种情况下,有用的数据都需要 2 个字节。
那么是什么让 reg1 占用 4 个字节?
完整代码示例:
#include "stdio.h"
// Register Structure using char
typedef struct {
// Reg0
struct _reg0_bitfieldsA {
char field0 : 1;
char field1 : 2;
char field2 ;
} reg0;
// Nextreg
char NextReg;
} regfileA_t;
// Register Structure using int
typedef struct {
// Reg1
struct _reg1_bitfieldsB {
int field0 : 1;
int field1 : 2;
char field2 ;
} reg1;
// Reg
char NextReg;
} regfileB_t;
regfileA_t regsA;
regfileB_t regsB;
int main(int argc, char const *argv[])
{
int* ptrA, *ptrB;
printf("sizeof(regsA) == %-0d\n",sizeof(regsA)); // prints 3 - as expected
printf("sizeof(regsB) == %-0d\n",sizeof(regsB)); // prints 8 - why ?
printf("\n");
printf("sizeof(regsA.reg0) == %-0d\n",sizeof(regsA.reg0)); // prints 2 - as epxected
printf("sizeof(regsB.reg0) == %-0d\n",sizeof(regsB.reg1)); // prints 4 - int bit fields tells the struct to use 4 bytes then.
printf("\n");
printf("addrof(regsA.reg0) == 0x%08x\n",(int)(®sA.reg0)); // 0x0804A028
printf("addrof(regsA.reg1) == 0x%08x\n",(int)(®sA.NextReg)); // 0x0804A02A = prev + 2
printf("addrof(regsB.reg0) == 0x%08x\n",(int)(®sB.reg1)); // 0x0804A020
printf("addrof(regsB.reg1) == 0x%08x\n",(int)(®sB.NextReg)); // 0x0804A024 = prev + 4 - my register is not at the righ place then.
printf("\n");
regsA.reg0.field0 = 1;
regsA.reg0.field1 = 3;
regsA.reg0.field2 = 0xAB;
regsB.reg1.field0 = 1;
regsB.reg1.field1 = 3;
regsB.reg1.field2 = 0xAB;
ptrA = (int*)®sA;
ptrB = (int*)®sB;
printf("regsA.reg0.value == 0x%08x\n",(int)(*ptrA)); // 0x0000AB07 (expected)
printf("regsB.reg0.value == 0x%08x\n",(int)(*ptrB)); // 0x0000AB07 (expected)
return 0;
}
当我第一次编写结构时,我希望 reg1 只占用 2 个字节,因此下一个寄存器位于偏移量 = 2。
标准的相关部分是C11/C17 6.7.2.1p11:
- 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. The order of allocation of bit-fields within a unit (high-order to low-order or low-order to high-order) is implementation-defined. The alignment of the addressable storage unit is unspecified.
有关
- A. bit-field shall have a type that is a qualified or unqualified version of _Bool, signed int, unsigned int, or some other implementation-defined type. It is implementation-defined whether atomic types are permitted.
并且您正在使用 char
意味着一般没有什么可讨论的 - 对于特定的实现,请查看编译器手册。 Here's the one for GCC.
从 2 个摘录中可以看出,实现可以自由使用 任何它想要的类型 来实现位域 - 它甚至可以对两者使用 int64_t
这些情况的结构大小为 16 字节。符合规范的实现 必须做的唯一一件事 是在剩余 space 足够的情况下将位打包到所选的可寻址存储单元中。
对于 System-V ABI on 386-compatible (32-bit processors) 上的 GCC,以下代表:
Plain bit-fields (that is, those neither signed
nor unsigned
) always have non- negative values. Although they may have type char
, short
, int
, long
, (which can have negative values),
these bit-fields have the same range as a bit-field of the same size
with the corresponding unsigned
type. Bit-fields obey the same
size and alignment rules as other structure and union members, with
the following additions:
- Bit-fields are allocated from right to left (least to most significant).
A bit-field must entirely reside in a storage unit appropriate for its declared type. Thus a bit-field never crosses its unit boundary.
Bit-fields may share a storage unit with other struct
/union
members, including members that are not bit-fields. Of course,
struct
members occupy different parts of the storage unit.
Unnamed bit-fields' types do not affect the alignment of a structure or union, although individual bit-fields' member offsets obey the
alignment constraints.
即在 System-V ABI 中,386,int f: 1
表示位域 f
必须在 int
中 。如果 space 的整个字节仍然存在,同一结构中的后续 char
将被 打包在此 int
中,即使它不是一个位-field.
利用这些知识,
的布局
struct {
int a : 1; // 1 bit - bit 0
int b : 2; // 2 bits - bits 2 down to 1
char c ; // 8 bits - bits 15 down to 8
} reg1;
将会
1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|a b b x x x x x|c c c c c c c c|x x x x x x x x|x x x x x x x x|
<------------------------------ int ---------------------------->
和
的布局
struct {
char a : 1; // 1 bit - bit 0
char b : 2; // 2 bits - bits 2 down to 1
char c ; // 8 bits - bits 15 down to 8
} reg1;
将会
1
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
|a b b x x x x x|c c c c c c c c|
<---- char ----><---- char ---->
因此存在棘手的边缘情况。比较这里的 2 个定义:
struct x {
short a : 2;
short b : 15;
char c ;
};
struct y {
int a : 2;
int b : 15;
char c ;
};
因为位域不能跨越单元边界,所以struct x
成员a
和b
需要去不同的short。那么就没有足够的space来容纳char c
,所以它必须在那个之后。并且整个结构必须针对 short
进行适当对齐,因此在 i386 上它将是 6 个字节。然而,后者会将 a
和 b
打包到 int
的最低 17 位中,并且由于 int
中仍然剩下一个完整的可寻址字节,因此 c
也将在这里打包,因此 sizeof (struct y)
将是 4.
最后,您必须真正指定 int
或 char
是否已签名 - 默认值可能不是您所期望的!标准将其留给实现,GCC 有一个编译时开关来更改它们。
在 C 中使用位域时,我发现了与用于声明字段的实际类型有关的差异,这是我没有预料到的。
我没有找到任何明确的解释。现在,问题已确定,因此尽管没有明确的答复,但此 post 可能对面临相同问题的任何人都有用。 不过,如果有人能给出正式的解释,那就太好了。
下面的结构,占用内存2个字节。
struct {
char field0 : 1; // 1 bit - bit 0
char field1 : 2; // 2 bits - bits 2 down to 1
char field2 ; // 8 bits - bits 15 down to 8
} reg0;
这个占内存4个字节,请问为什么?
struct {
int field0 : 1; // 1 bit - bit 0
int field1 : 2; // 2 bits - bits 2 down to 1
char field2 ; // 8 bits - bits 15 down to 8
} reg1;
在这两种情况下,这些位在内存中的组织方式相同:字段 2 始终将位 15 降低为 8。
我试图找到一些关于这个主题的文献,但仍然无法得到明确的解释。
我能找到的两个最合适的链接是:
- http://www.catb.org/esr/structure-packing/
- http://www.msg.ucsf.edu/local/programs/IBM_Compilers/C:C++/html/language/ref/clrc03defbitf.htm
然而,none 真正解释了为什么第二个结构占用 4 个字节。实际上仔细阅读,我什至希望该结构占用 2 个字节。
在这两种情况下,
- field0占1位
- field1占用2位
- field2 占用 8 位,并对齐到第一个可用字节地址
因此,在这两种情况下,有用的数据都需要 2 个字节。
那么是什么让 reg1 占用 4 个字节?
完整代码示例:
#include "stdio.h"
// Register Structure using char
typedef struct {
// Reg0
struct _reg0_bitfieldsA {
char field0 : 1;
char field1 : 2;
char field2 ;
} reg0;
// Nextreg
char NextReg;
} regfileA_t;
// Register Structure using int
typedef struct {
// Reg1
struct _reg1_bitfieldsB {
int field0 : 1;
int field1 : 2;
char field2 ;
} reg1;
// Reg
char NextReg;
} regfileB_t;
regfileA_t regsA;
regfileB_t regsB;
int main(int argc, char const *argv[])
{
int* ptrA, *ptrB;
printf("sizeof(regsA) == %-0d\n",sizeof(regsA)); // prints 3 - as expected
printf("sizeof(regsB) == %-0d\n",sizeof(regsB)); // prints 8 - why ?
printf("\n");
printf("sizeof(regsA.reg0) == %-0d\n",sizeof(regsA.reg0)); // prints 2 - as epxected
printf("sizeof(regsB.reg0) == %-0d\n",sizeof(regsB.reg1)); // prints 4 - int bit fields tells the struct to use 4 bytes then.
printf("\n");
printf("addrof(regsA.reg0) == 0x%08x\n",(int)(®sA.reg0)); // 0x0804A028
printf("addrof(regsA.reg1) == 0x%08x\n",(int)(®sA.NextReg)); // 0x0804A02A = prev + 2
printf("addrof(regsB.reg0) == 0x%08x\n",(int)(®sB.reg1)); // 0x0804A020
printf("addrof(regsB.reg1) == 0x%08x\n",(int)(®sB.NextReg)); // 0x0804A024 = prev + 4 - my register is not at the righ place then.
printf("\n");
regsA.reg0.field0 = 1;
regsA.reg0.field1 = 3;
regsA.reg0.field2 = 0xAB;
regsB.reg1.field0 = 1;
regsB.reg1.field1 = 3;
regsB.reg1.field2 = 0xAB;
ptrA = (int*)®sA;
ptrB = (int*)®sB;
printf("regsA.reg0.value == 0x%08x\n",(int)(*ptrA)); // 0x0000AB07 (expected)
printf("regsB.reg0.value == 0x%08x\n",(int)(*ptrB)); // 0x0000AB07 (expected)
return 0;
}
当我第一次编写结构时,我希望 reg1 只占用 2 个字节,因此下一个寄存器位于偏移量 = 2。
标准的相关部分是C11/C17 6.7.2.1p11:
有关
- 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. The order of allocation of bit-fields within a unit (high-order to low-order or low-order to high-order) is implementation-defined. The alignment of the addressable storage unit is unspecified.
- A. bit-field shall have a type that is a qualified or unqualified version of _Bool, signed int, unsigned int, or some other implementation-defined type. It is implementation-defined whether atomic types are permitted.
并且您正在使用 char
意味着一般没有什么可讨论的 - 对于特定的实现,请查看编译器手册。 Here's the one for GCC.
从 2 个摘录中可以看出,实现可以自由使用 任何它想要的类型 来实现位域 - 它甚至可以对两者使用 int64_t
这些情况的结构大小为 16 字节。符合规范的实现 必须做的唯一一件事 是在剩余 space 足够的情况下将位打包到所选的可寻址存储单元中。
对于 System-V ABI on 386-compatible (32-bit processors) 上的 GCC,以下代表:
Plain bit-fields (that is, those neither
signed
norunsigned
) always have non- negative values. Although they may have typechar
,short
,int
,long
, (which can have negative values), these bit-fields have the same range as a bit-field of the same size with the correspondingunsigned
type. Bit-fields obey the same size and alignment rules as other structure and union members, with the following additions:
- Bit-fields are allocated from right to left (least to most significant).
A bit-field must entirely reside in a storage unit appropriate for its declared type. Thus a bit-field never crosses its unit boundary.
Bit-fields may share a storage unit with other
struct
/union
members, including members that are not bit-fields. Of course,struct
members occupy different parts of the storage unit.Unnamed bit-fields' types do not affect the alignment of a structure or union, although individual bit-fields' member offsets obey the alignment constraints.
即在 System-V ABI 中,386,int f: 1
表示位域 f
必须在 int
中 。如果 space 的整个字节仍然存在,同一结构中的后续 char
将被 打包在此 int
中,即使它不是一个位-field.
利用这些知识,
的布局struct {
int a : 1; // 1 bit - bit 0
int b : 2; // 2 bits - bits 2 down to 1
char c ; // 8 bits - bits 15 down to 8
} reg1;
将会
1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|a b b x x x x x|c c c c c c c c|x x x x x x x x|x x x x x x x x|
<------------------------------ int ---------------------------->
和
的布局struct {
char a : 1; // 1 bit - bit 0
char b : 2; // 2 bits - bits 2 down to 1
char c ; // 8 bits - bits 15 down to 8
} reg1;
将会
1
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
|a b b x x x x x|c c c c c c c c|
<---- char ----><---- char ---->
因此存在棘手的边缘情况。比较这里的 2 个定义:
struct x {
short a : 2;
short b : 15;
char c ;
};
struct y {
int a : 2;
int b : 15;
char c ;
};
因为位域不能跨越单元边界,所以struct x
成员a
和b
需要去不同的short。那么就没有足够的space来容纳char c
,所以它必须在那个之后。并且整个结构必须针对 short
进行适当对齐,因此在 i386 上它将是 6 个字节。然而,后者会将 a
和 b
打包到 int
的最低 17 位中,并且由于 int
中仍然剩下一个完整的可寻址字节,因此 c
也将在这里打包,因此 sizeof (struct y)
将是 4.
最后,您必须真正指定 int
或 char
是否已签名 - 默认值可能不是您所期望的!标准将其留给实现,GCC 有一个编译时开关来更改它们。