Ale*_*osa 23 c gcc struct field bit
我正在研究C语言的基础知识.我用位字段到达了结构章节.本书展示了一个包含两种不同类型数据的结构示例:各种bool和各种无符号整数.
该书声明该结构的大小为16位,并且在不使用填充的情况下,该结构将测量10位.
这是本书在示例中使用的结构:
#include <stdio.h>
#include <stdbool.h>
struct test{
bool opaque : 1;
unsigned int fill_color : 3;
unsigned int : 4;
bool show_border : 1;
unsigned int border_color : 3;
unsigned int border_style : 2;
unsigned int : 2;
};
int main(void)
{
struct test Test;
printf("%zu\n", sizeof(Test));
return 0;
}
Run Code Online (Sandbox Code Playgroud)
为什么在我的编译器上使用填充完全相同的结构测量16 个字节(而不是位)而没有填充的16个字节?
我正在使用
GCC (tdm-1) 4.9.2 compiler;
Code::Blocks as IDE.
Windows 7 64 Bit
Intel CPU 64 bit
Run Code Online (Sandbox Code Playgroud)
这是我得到的结果:
这是一个示例页面的图片:
int*_*jay 28
Microsoft ABI以与GCC通常在其他平台上执行的方式不同的方式布局位域.您可以选择将Microsoft兼容的布局与-mms-bitfields选项一起使用,或者将其禁用-mno-ms-bitfields.您的GCC版本可能-mms-bitfields默认使用.
根据文档,-mms-bitfields何时启用:
- 每个数据对象都有一个对齐要求.除结构,联合和数组之外的所有数据的对齐要求是对象的大小或当前打包大小(使用aligned属性或pack pragma指定),以较小者为准.对于结构,联合和数组,对齐要求是其成员的最大对齐要求.为每个对象分配一个偏移量,以便:offset%alignment_requirement == 0
- 如果整数类型具有相同的大小并且如果下一个比特字段适合当前分配单元而不跨越公共边界施加的边界,则相邻的比特字段被打包到相同的1字节,2字节或4字节分配单元中位域的对齐要求.
因为bool和unsigned int具有不同的尺寸,将它们装入并分别对齐,这显着地增加该结构的大小.对齐unsigned int是4个字节,并且必须在结构的中间重新对齐三次导致总大小为16字节.
您可以通过更改拿到书的相同的行为bool来unsigned int,或通过指定-mno-ms-bitfields(虽然这将意味着你不能与微软编译器编译代码的互操作).
请注意,C标准未指定如何布置位域.所以你的书所说的对于某些平台可能是正确的,但对所有平台都不是.
作为描述C语言标准的规定,您的文本提出了不合理的主张.具体而言,标准不仅没有说unsigned int是任何类型结构的基本布局单元,它明确否认对存储位域表示的存储单元的大小有任何要求:
实现可以分配足够大的任何可寻址存储单元来保持位字段.
该文本还假设标准不支持填充.AC实现可以在a的任何或所有元素和位域存储单元之后包括任意数量的填充struct,包括最后一个.实现通常使用此自由来解决数据对齐问题,但C不限制填充到该用途.这与您的文本称为"填充"的未命名位域完全分开.
然而,我想这本书应该受到赞扬,因为它避免了令人不安的常见误解,即C要求声明的位字段数据类型与其表示所在的存储单元的大小有关.该标准没有这样的关联.
为什么在我的编译器上使用填充完全相同的结构测量16个字节(而不是位)而没有填充的16个字节?
为了尽可能多地减少文本,它确实区分了成员占用的数据位数(总共16位,6个属于未命名的位域)和实例的总体大小struct.似乎断言整体结构将是a的大小,unsigned int在它描述的系统上显然是32位,并且对于两个版本的结构都是相同的.
原则上,您的观察大小可以通过使用128位存储单元进行位域实现来解释.在实践中,它可能使用一个或多个较小的存储单元,因此每个结构中的一些额外空间可归因于实现提供的填充,例如我在上面触摸.
C实现在所有结构类型上施加最小大小是非常常见的,因此在必要时将表示填充到该大小.通常,此大小符合系统支持的任何数据类型的最严格的对齐要求,尽管这也是实现考虑因素,而不是语言的要求.
结论:只有依靠实现细节和/或扩展,才能预测a的确切大小struct,无论是否存在位域成员.
令我惊讶的是,一些GCC 4.9.2在线编译器之间似乎存在差异.首先,这是我的代码:
#include <stdio.h>
#include <stdbool.h>
struct test {
bool opaque : 1;
unsigned int fill_color : 3;
unsigned int : 4;
bool show_border : 1;
unsigned int border_color : 3;
unsigned int border_style : 2;
unsigned int : 2;
};
struct test_packed {
bool opaque : 1;
unsigned int fill_color : 3;
unsigned int : 4;
bool show_border : 1;
unsigned int border_color : 3;
unsigned int border_style : 2;
unsigned int : 2;
} __attribute__((packed));
int main(void)
{
struct test padding;
struct test_packed no_padding;
printf("with padding: %zu bytes = %zu bits\n", sizeof(padding), sizeof(padding) * 8);
printf("without padding: %zu bytes = %zu bits\n", sizeof(no_padding), sizeof(no_padding) * 8);
return 0;
}
Run Code Online (Sandbox Code Playgroud)
现在,来自不同编译器的结果.
来自WandBox的GCC 4.9.2:
with padding: 4 bytes = 32 bits
without padding: 2 bytes = 16 bits
Run Code Online (Sandbox Code Playgroud)
来自http://cpp.sh/的 GCC 4.9.2 :
with padding: 4 bytes = 32 bits
without padding: 2 bytes = 16 bits
Run Code Online (Sandbox Code Playgroud)
但
来自theonlinecompiler.com的GCC 4.9.2:
with padding: 16 bytes = 128 bits
without padding: 16 bytes = 128 bits
Run Code Online (Sandbox Code Playgroud)
(正确地编译你需要chagne %zu到%u)
编辑
@interjay的答案可能会解释这一点.当我-mms-bitfields从WandBox 添加到GCC 4.9.2时,我得到了这个:
with padding: 16 bytes = 128 bits
without padding: 16 bytes = 128 bits
Run Code Online (Sandbox Code Playgroud)
C标准没有描述有关如何将变量放入内存的所有细节.这为优化提供了空间,具体取决于所使用的平台.
为了让自己了解事物在内存中的位置,你可以这样做:
#include <stdio.h>
#include <stdbool.h>
struct test{
bool opaque : 1;
unsigned int fill_color : 3;
unsigned int : 4;
bool show_border : 1;
unsigned int border_color : 3;
unsigned int border_style : 2;
unsigned int : 2;
};
int main(void)
{
struct test Test = {0};
int i;
printf("%zu\n", sizeof(Test));
unsigned char* p;
p = (unsigned char*)&Test;
for(i=0; i<sizeof(Test); ++i)
{
printf("%02x", *p);
++p;
}
printf("\n");
Test.opaque = true;
p = (unsigned char*)&Test;
for(i=0; i<sizeof(Test); ++i)
{
printf("%02x", *p);
++p;
}
printf("\n");
Test.fill_color = 3;
p = (unsigned char*)&Test;
for(i=0; i<sizeof(Test); ++i)
{
printf("%02x", *p);
++p;
}
printf("\n");
return 0;
}
Run Code Online (Sandbox Code Playgroud)
在ideone上运行它(https://ideone.com/wbR5tI)我得到:
4
00000000
01000000
07000000
Run Code Online (Sandbox Code Playgroud)
所以我可以看到opaque并且fill_color都在第一个字节中.在Windows机器上运行完全相同的代码(使用gcc)给出:
16
00000000000000000000000000000000
01000000000000000000000000000000
01000000030000000000000000000000
Run Code Online (Sandbox Code Playgroud)
所以我在这里可以看到,opaque和fill_color是不是都在第一个字节.似乎opaque占用了4个字节.
这解释了总共得到16个字节,即bool每个字节需要4个字节,然后是4个字节,用于之间和之后的字段.
在作者定义结构之前,他说他想将位字段分成两个字节,因此将有一个字节包含填充相关信息的位域和一个字节用于边界相关信息.
为此,他添加(插入)一些未使用的位(位域):
unsigned int 4; // padding of the first byte
Run Code Online (Sandbox Code Playgroud)
他也填充第二个字节,但没有必要.
因此,在填充之前,将使用10位,并且在填充之后,定义了16位(但是没有使用它们).
bool表示1/0字段.作者接下来假设_BoolC99类型是别名bool.但似乎编译器在这里有点困惑.更换bool用unsigned int会解决它.来自C99:
6.3.2:如果可以使用int或unsigned int,则可以在表达式中使用以下内容:
- 类型的位字段
_Bool,int,signed int,或unsigned int.