我继承了一些在结构中使用位域的代码:
typedef _my_flags {
unsigned int x_ida:1;
unsigned int x_foo:6;
unsigned int x_bar:6;
unsigned int x_bonzo:6;
unsigned int x_pizza:6;
unsigned int x_jack:1;
unsigned int x_flashed:1;
unsigned int x_flabberghasted:1;
} t_my_flags;
typdef _my_struct {
short cat;
int foo, bar, bla;
t_my_flags flags; /* ... */
char* name;
}
这两种结构都是公共API的一部分。
现在我迫切需要添加一些额外的标志(实际上只有一个),所以我想知道添加扩展t_my_flags
结构是否安全:
typedef _my_flags {
unsigned int x_ida:1;
unsigned int x_foo:6;
unsigned int x_bar:6;
unsigned int x_bonzo:6;
unsigned int x_pizza:6;
unsigned int x_jack:1;
unsigned int x_flashed:1;
unsigned int x_flabberghasted:1;
unsigned int x_ready:1; /* new member */
} t_my_flags;
EDIT这些结构体用于动态库(因此不必担心将数据持久化到文件系统或通过网络发送)
我担心会破坏二进制兼容性。
我的修改将把t_my_flags
结构体从28位增加到29位,所以我假设它将以32位整数的形式存在。所以结构体的总大小不会改变。
“我不知道,我真的不知道。”
当然,这应该运行在各种架构(x86_64,i386,arm 32,arm 64,s390 x,ppc)上的所有主要操作系统(Linux,macOS,Windows)上,使用未知的编译器(显然是gcc/clang和MSVC,不知道其他;我们坚持使用C89是有原因的...)。
编辑我们可以假设在任何给定的操作系统/架构上使用相同的编译器(好吧,对于Windows,我们使用gcc的-mms-bitfields
标志,它应该给我们与MSVC的位域兼容性)。上面给出的体系结构/操作系统/编译器列表仅仅是为了表明我对我的用例在不同环境中的行为感兴趣。我不太关心不同环境之间的互操作性)。
那么:如何在不改变二进制兼容性的情况下添加新的位域而不改变整体大小呢?
1条答案
按热度按时间vlju58qv1#
添加一个新的位域到我的C结构会破坏ABI吗?
ABI是环境的属性,而不是程序或库的属性。我猜你的意思是问你的改变是否会破坏 * 二进制兼容性 *,这是不同的东西。
既然你说...
这两种结构都是公共API的一部分。
我推断代码是共享库的一部分(而不是静态库)。这确实是与二进制兼容性相关的主要情况之一,因为对于大多数其他目的,二进制兼容性是由其他代码不会看到任何此类更改的事实提出的,除非您重新编译它。
基本的答案是“视情况而定”,但更诚实的答案是“可能如此”,这是最安全的解释为“是”。
我的修改会将
t_my_flags
结构体从28位增加到29位,所以我假设它将以32位整数的形式存在。所以结构体的总大小不会改变。一般来说,这是一个“可能”的结果,但绝不是一个“保证”的结果。这是目标系统的ABI要解决的问题之一。
“我不知道,我真的不知道。”
同样,相关的ABI会告诉你结构布局的细节,包括位域。C语言规范对此没有太多说明,但是 * 确实 * 提供了其他标志的位置在其可寻址存储单元内改变的可能性。同样,这个问题将由目标的ABI来解决-不同的ABI不一定以相同的方式解决。
在任何情况下,如果作为公共API的一部分意味着这些类型的定义公开给库用户,并且库客户端被允许/期望直接访问成员,那么你必须假设对结构成员的任何更改都会破坏二进制兼容性。
即使布局相对于原始成员没有改变,您现在也有了一个老客户不知道的额外成员。这样,您就不能依赖库的用户在分配自己的示例时将该新成员设置为适当的值。如果你(已经)坚持用户依赖于库提供的某种初始化函数,这会有所缓解,但即使你这样做,“我们的更新破坏了你的程序,因为它错误地使用了我们的库”是一个硬推销,无论多么真实。