共享库中的位域可移植性

Bit fields portability in a shared library

我很难理解 C 中位域的可移植性。假设我有一个共享库,它只包含两个文件,libfoobar.h(public header)和 libfoobar.c,简单内容如下:

libfoobar.h:

typedef struct some_bitfield_T {
    unsigned char foo:3;
    unsigned char bar:2;
    unsigned char tree:2;
    unsigned char window:1;
} some_bitfield;

extern unsigned int some_function (some_bitfield input);

libfoobar.c:

#include "libfoobar.h"

unsigned int some_function (some_bitfield input) {
    return input.foo * 3 + input.bar + input.tree + 1 - input.window;
}

编译并安装库后,我用一个名为 test.

的程序对其进行了测试

test.c:

#include <stdio.h>
#include <libfoobar.h>

int main () {
    some_bitfield my_attempt = {
        .foo = 6,
        .bar = 3,
        .tree = 1,
        .window = 1
    };
    unsigned int some_number = some_function(my_attempt);
    printf("Here is the result: %u\n", some_number);
    return 0;
}

上面的 test 程序是否有可能产生与以下输出不同的东西?

Here is the result: 22

如果是,什么时候?如果库是由我以外的其他人编译的怎么办?如果我对库和 test 程序使用不同的编译器怎么办?

位域是实现定义的,不可移植。但对于大多数相关平台,它们的 extraction/packing 由 ABI 明确指定,因此它们可以安全地用于共享库。

例如:

  • ARM EABI 在 7.1.7 "Bit-fields".
  • 中指定它们
  • i386 ABI 在第 3-6 页指定它们+
  • x86-64 在第 15 页指定它们+
  • MIPS 在第 3-7 页指定它们+

这里是 C11 标准的相关部分:

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.

§6.7.2.1 第 11 点

这意味着编译器可以使用它喜欢的任何合适的类型来保存位域,并且它们将与每个位域相邻并按照它们被定义的顺序。

但是,编译器可以自行选择是从高到低还是从低到高对它们进行排序。如果它 space 中的 运行 或者分配一个新的存储单元,它也可以选择重叠位域(在你的情况下不是问题,你只有 8 位)。

考虑到以上几点,我们可以回答您的问题。如果程序和库是使用相同的编译器实现编译的,您只能保证您的测试程序会给出正确的答案。如果您使用两个不同的编译器,即使它们都使用(比如)unsigned char 来存储位字段,一个可以从字节的顶部开始,另一个可以从底部开始。

在实践中,正如 ensc 上面所说,平台 ABI 可能会定义一个位字段打包和排序标准,这使得同一平台上的编译器之间可以正常工作,但原则上不能保证这一点。

I have difficulty to understand the portability of bit fields in C

嗯,没什么好理解的 - 位域不可移植。

Is there any possibility that the test program above will produce anything different than the following output?

最常见的情况是用户 space 和内核 space 之间的通信。有时通信使用指向结构的指针。 Headers 由库实现者编写,如 glibc,包装内核系统调用有时会复制在内核源代码树中定义的相同结构。为了进行正确的通信,这些结构中的填充必须在两侧相同 - 在编译内核时在内核端和在编译内核多年后我们高兴地编译我们自己的项目时在用户 space 端。

在大多数架构 + 操作系统上都存在一个“ABI”——一组规则,这些规则还决定了结构应该如何填充以及 bit-fields 应该如何打包。编译器 可能 遵守或不遵守该 ABI。当 gcc 用于 cross-compile for windows from linux 时,例如。 __attribute__ ((ms_struct)) 需要用于确保使用与微软编译器的恶作剧兼容的正确结构打包。

所以回答:Is there any possibility - 当然有,不同的编译器标志或设置可能会导致结构成员之间的不同打包或填充所以我可以用 gcc -fpack-struct=100 编译程序,你编译你的共享gcc -fpack-struct=20 和哎呀图书馆。但这不仅限于结构填充 - 其他人可以使用具有 64 位而不是 32 位的 unsigned int 编译您的程序,因此函数的 return 值可能是意外的。

If yes, when?

当使用不兼容的代码生成方法来创建依赖于指定 ABI 进行通信的机器代码时。从实际的角度来看,这会发生吗?每个 linux 系统在 /usr/lib 中有 个共享库。

What if the library is compiled by someone else other than me?

那他就可以为所欲为了。但是如果您真的愿意,您可以表明您的共享库遵循一些通用的 ABI 标准。

What if I use different compilers for the library and the test program?

然后请务必阅读该编译器文档以确保它遵循您需要的通用 ABI。

阅读:How to Write Shared Libraries. Ulrich Drepper - 第 3 节似乎相关。