零长度位域的实际使用
Posted
技术标签:
【中文标题】零长度位域的实际使用【英文标题】:Practical Use of Zero-Length Bitfields 【发布时间】:2011-05-16 20:59:52 【问题描述】:我不太确定 C,但 C++ 允许长度为 0 的未命名位域。例如:
struct X
int : 0;
;
问题一:你能想到什么实际用途?
问题二:您知道哪些实际用途(如果有)?
编辑冰罪回答后的例子
编辑: 好的,感谢当前的答案,我现在知道了理论目的。但是问题是关于实际用途的,所以它们仍然成立:)
【问题讨论】:
C99 允许零长度数组更好地支持动态大小的结构。 @roe:是的,嗯,它相当于 0 长度的动态分配数组,非常有用。我认为这里的问题是位域长度必须是编译时常量。 @roe:您的评论不正确。 C 不允许char a[0];
在任何版本的标准中。另一方面,char a[];
在 C99 的结构中是允许的;它称为灵活数组成员,必须出现在末尾。 char a[];
不是“char a[0];
的简写”。
@R,我错了;使用零是 GCC 特定的扩展。从技术上讲,它仍然是一个零长度数组,尽管最初的问题是不可能的。我知道它必须在结构的末尾(其他任何地方都没有任何意义)。
@R: 太糟糕了 C 要求数组具有非零大小,因为最常见的 hack 的流行解决了对零大小数组的禁令,这会阻止编译器制作一些其他有用的东西优化。例如,如果 struct foo 以“int bar[1]”结尾,则表达式“foo.bar[i]”可以合法地替换为“foo.bar[0]”,这可以在编译时进行评估(因为任何其他“i”的值将调用未定义的行为)。当然,struct hack 非常普遍,任何利用这种优化的编译器都会破坏现实世界的代码。
【参考方案1】:
C11 标准现在允许包含零长度位域。这是 C 委员会草案 (N1570) 中的一个示例,我相信它说明了一个实际用法。
3.14 内存位置 ... 4. 示例结构声明为
struct char a; int b:5, c:11, :0, d:8; struct int ee:8; e;
包含四个独立的内存位置:成员
a
和位域d
和e.ee
都是独立的内存位置,可以同时修改而不会相互干扰。位域b
和c
一起构成第四个内存位置。位域b
和c
不能同时修改,但例如b
和a
可以。
因此在位域c
和d
之间包含零长度位域也允许同时修改b
和d
。
【讨论】:
【参考方案2】:struct X int : 0; ;
在 C 中是未定义的行为。
见(强调我的):
(C99, 6.7.2.1p2) “结构或联合说明符中的结构声明列表的存在在翻译单元内声明了一个新类型。结构声明列表是一个序列结构或联合成员的声明。如果 struct-declaration-list 不包含命名成员,则行为未定义"
(C11也一样。)
您可以使用宽度为0
的未命名位域,但如果结构中没有其他命名成员则不能。
例如:
struct W int a:1; int :0; ; // OK
struct X int :0; ; // Undefined Behavior
顺便说一句,gcc
使用-pedantic
发出诊断(C 标准不要求)。
另一方面:
struct X int :0; ;
在 GNU C 中定义。例如,Linux 内核 (include/linux/bug.h
) 使用它在条件为真时使用以下宏强制编译错误:
#define BUILD_BUG_ON_ZERO(e) (sizeof(struct int:-!!(e); ))
【讨论】:
+1;也许您可以提到***.com/questions/9229601/what-is-in-c-code 更详细地描述了该宏。 为了BUILD_BUG_ON_ZERO
,我希望该结构仍然在GnuC中定义,我在自己的代码中使用static_assert
来确保它确实如此!它当然似乎没有打开,比如说,msvc。在 godbolt.org 下使用 v19.14,sizeof:struct int:0;
为 4(在 gcc 下为 0),而奇怪的是,struct char c; int:0;
和 struct int:0; char c;
的 sizeof 均为 1(在 gcc 下均为 4)。后者在我看来就像一个错误。
我很好奇你怎么知道struct X int :0; ;
是在 GCC 中定义的,而不是在 UB 中定义的?谢谢!【参考方案3】:
您使用零长度位域作为一种 hacky 方式让您的编译器布局结构以匹配某些外部要求,无论是另一个编译器或架构的布局概念(跨平台数据结构,例如在二进制文件格式)或位级标准的要求(网络数据包或指令操作码)。
一个真实的例子是 NeXT 将 xnu 内核从 Motorola 68000 (m68k) 架构移植到 i386 架构。 NeXT 有一个工作的 m68k 版本的内核。当他们将其移植到 i386 时,他们发现 i386 的对齐要求与 m68k 的不同之处在于 m68k 机器和 i386 机器在 NeXT 供应商特定 BOOTP 结构的布局上没有达成一致。为了使 i386 结构布局与 m68k 一致,他们添加了一个长度为零的未命名位域,以强制 NV1
结构/nv_U
联合为 16 位对齐。
以下是 Mac OS X 10.6.5 xnu 源代码中的相关部分:
/* from xnu/bsd/netinet/bootp.h */
/*
* Bootstrap Protocol (BOOTP). RFC 951.
*/
/*
* HISTORY
*
* 14 May 1992 ? at NeXT
* Added correct padding to struct nextvend. This is
* needed for the i386 due to alignment differences wrt
* the m68k. Also adjusted the size of the array fields
* because the NeXT vendor area was overflowing the bootp
* packet.
*/
/* . . . */
struct nextvend
u_char nv_magic[4]; /* Magic number for vendor specificity */
u_char nv_version; /* NeXT protocol version */
/*
* Round the beginning
* of the union to a 16
* bit boundary due to
* struct/union alignment
* on the m68k.
*/
unsigned short :0;
union
u_char NV0[58];
struct
u_char NV1_opcode; /* opcode - Version 1 */
u_char NV1_xid; /* transcation id */
u_char NV1_text[NVMAXTEXT]; /* text */
u_char NV1_null; /* null terminator */
NV1;
nv_U;
;
【讨论】:
【参考方案4】:标准 (9.6/2) 仅允许长度为 0 的位字段作为特殊情况:
作为一种特殊情况,一个未命名的 宽度为零的位域 指定下一个的对齐方式 分配单元的位域 边界。 仅在声明 未命名的位域可能 常量表达式是一个相等的值 归零。
这个引用中描述了唯一的用途,虽然我还没有在实际代码中遇到过它。
为了记录,我只是在 VS 2010 下尝试了以下代码:
struct X
int i : 3, j : 5;
;
struct Y
int i : 3, : 0, j : 5; // nice syntax huh ?
;
int main()
std::cout << sizeof(X) << " - " << sizeof(Y) << std::endl;
我机器上的输出确实是:4 - 8
。
【讨论】:
一个实际的例子:***.com/questions/9229601/what-is-it-in-c-code【参考方案5】:这是来自 MSDN 并且没有标记为 Microsoft 特定,所以我猜这是常见的 C++ 标准:
宽度为 0 的未命名位域强制下一个位域与下一个类型边界对齐,其中 type 是成员的类型。
【讨论】:
以上是关于零长度位域的实际使用的主要内容,如果未能解决你的问题,请参考以下文章