7

可以安全地假设在任何实际可用的平台 ABI 中定义了哪些项目?

  1. 的价值CHAR_BIT

  2. 尺寸、对齐要求和对象表示:

    1. void*, size_t,ptrdiff_t
    2. unsigned charsigned char
    3. intptr_tuintptr_t
    4. float,doublelong double
    5. shortlong long
    6. intlong(但在这里我期待一个“不”)
    7. 指向平台 ABI 为其指定这些属性的对象类型的指针
    8. 指向函数的指针,其类型仅涉及平台 ABI 为其指定这些属性的类型
  3. 空对象指针的对象表示

  4. 空函数指针的对象表示

例如,如果我有一个发布此函数的库(由未知但符合 ABI 的编译器编译):

void* foo(void *bar, size_t baz, void* (*qux)());

无论我使用什么编译器,我都可以假设能够在我的程序中安全地调用它吗?

或者,反过来说,如果我正在编写一个库,是否有一组类型,如果我将库的公共接口限制为这个集合,它将保证在它构建的所有平台上都可用?

4

3 回答 3

3

我看不出你怎么能期望任何库都兼容。如果可能的话,就不会有这么多编译的库变体。

例如,只要正确设置调用,就可以从 16 位程序调用 64 位库。但是您必须知道您正在调用基于 64 位的库。

可移植性是一个备受关注的目标,但很少有人真正实现它。经过 30 多年的系统级、固件和应用程序编程,我认为这更像是一个幻想而不是一个目标。不幸的是,硬件迫使我们针对硬件进行优化。因此,当我编写库时,我使用以下内容:

  1. 为 ABI 编译
  2. 使用指向结构的指针用于所有函数调用的输入和输出:

    int lib_func(struct *input, struct *output);
    

返回的 int 仅指示错误。我使所有错误代码都是唯一的。我要求用户在使用该库之前调用一个 init 函数。用户称其为:

    lib_init(sizeof(int), sizeof(char *), sizeof(long), sizeof(long long));

这样我就可以决定是否会有任何麻烦或在需要时修改任何假设。除了版本号之外,我还添加了一个功能,允许用户了解我的数据大小和对齐方式。

这并不是说用户或我被期望“即时”修改代码或花费大量 CPU 功率重新设计结构。但这允许应用程序绝对确保它与我兼容,反之亦然。

我过去采用的另一个选项是在我的库中简单地包含几个入口点函数。例如:

   int lib_func32();
   int lib_func16();
   int lib_func64();

它给你带来了一些麻烦,但你可以使用预处理器来修复它:

   #ifdef LIB_USE32
      #define  lib_function  lib_func32
   #endif

您可以对数据结构执行相同的操作,但我建议无论 CPU 大小如何都使用相同大小的数据结构——除非性能是重中之重。再次,回到硬件!

我探索的最后一个选项是是否具有所有大小和样式的输入函数,将输入转换为我的库的期望,以及我的库的输出。

例如,您lib_func32(&input, &output)可以编译为期望 32 位对齐的 32 位指针,但它将 32 位结构转换为您的内部 64 位结构,然后调用您的 64 位函数。当它返回时,它将 64 位结构重新格式化为调用者所指向的 32 位等效结构。

   int lib_func32(struct *input32, struct *output32)
   {
   struct input64;
   struct output64;
   int    retval;

       lib_convert32_to_64(input32, &input64);

       retval = lib_func64(&input64, &output64);

       lib_convert64_to_32(&output64, output32);

       return(retval);
   }

总之,完全便携的解决方案是不可行的。即使您从完全可移植性开始,最终您也将不得不偏离。这是事情真正变得混乱的时候。您会因偏差而破坏您的风格,从而破坏您的文档并使用户感到困惑。我认为最好从一开始就计划好。

硬件总是会导致你有偏差。只需考虑“字节顺序”造成的麻烦——更不用说每天交换字节顺序所使用的 CPU 周期数。

于 2013-06-27T17:02:09.230 回答
2

C 标准在附录中包含一个完整的部分,总结如下:

J.3 实现定义的行为

一个完全随机的子集:

  • 一个字节中的位数

  • signed char和中的哪unsigned char一个与char

  • 多字节和宽字符串的文本编码

  • 有符号整数表示

  • 将指针转换为整数的结果,反之亦然 (6.3.2.3)。请注意,这意味着任何指针,而不仅仅是对象指针。


更新:为了解决您关于 ABI 的问题:ABI(应用程序二进制接口)不是一个标准化的概念,并且在任何地方都没有说实现甚至必须指定 ABI。ABI 的成分部分是语言的实现定义的行为(尽管不是全部;例如,有符号到无符号的转换是实现定义的,但不是 ABI 的一部分),以及大部分实现定义的方面语言由硬件决定(例如有符号整数表示、浮点表示、指针大小)。

但是,ABI 更重要的方面是函数调用的工作方式,即参数的存储位置,谁负责清理内存等。对于两个编译器来说,就这些约定达成一致对于他们的代码来说是至关重要的。二进制兼容。

在实践中,ABI 通常是结果的一个实现。一旦编译器完成,它会根据它的实现来确定一个 ABI。它可能会记录此 ABI,其他编译器以及同一编译器的未来版本可能希望遵守这些约定。对于 x86 上的 C 实现,这工作得相当好,并且只有少数(通常是有据可查的)免费参数需要传达以使代码可互操作。但是对于其他语言,尤其是 C++,你有一个完全不同的画面:C++ 的标准 ABI 根本没有任何东西可以接近。Microsoft 的编译器在每次发布时都会破坏 C++ ABI。GCC 努力保持跨版本的 ABI 兼容性并使用已发布的 Itanium ABI(具有讽刺意味的是,对于现在已死的架构)。其他编译器可能会做自己的,完全不同的事情。实现,例如您是否string包含一个、两个或三个指针,以及按什么顺序?)

总结一下:编译器 ABI 的许多方面,尤其是与 C 相关的方面,都是由硬件架构决定的。相同硬件的不同 C 编译器应该生成兼容的二进制代码,只要函数调用约定等某些方面能够正确通信。然而,对于更高级的语言,所有的赌注都没有了,两个不同的编译器是否可以产生可互操作的代码必须根据具体情况来决定。

于 2013-07-01T23:55:07.233 回答
1

如果我正确理解了您的需求,那么 uint 样式的样式是唯一可以为您提供二进制兼容性保证的样式,并且导致 int、char 会,但其他样式往往会有所不同。即long在Windows 和Linux 上,Windows 认为它​​是4byte 而Linux 是8byte。如果你真的依赖 ABI,你必须规划你将要交付的平台,并且可能会使用typedefs 来使事情标准化和可读。

于 2013-06-30T12:45:21.330 回答