可以安全地假设在任何实际可用的平台ABI中定义了哪些项目?
CHAR_BIT
尺寸,对齐要求和对象表示:
void*
,size_t
,ptrdiff_t
unsigned char
和signed char
intptr_t
和uintptr_t
float
,double
和long double
short
和long long
int
和long
(但此处我希望“不”)空对象指针的对象表示
空函数指针的对象表示
例如,如果我有一个库(由未知但符合ABI的编译器编译)发布此函数:
void* foo(void *bar, size_t baz, void* (*qux)());
我可以假设能够在我的程序中安全地调用它而不管我使用的编译器吗?
或者,反过来说,如果我正在编写一个库,是否有一组类型,如果我将库的公共接口限制为此集合,它将保证可在其构建的所有平台上使用?
答案 0 :(得分:3)
我不知道你怎么能期望任何库普遍兼容。如果可能的话,那么库的编译变体就不会那么多了。
例如,只要正确设置了呼叫,就可以从16位程序中调用64位库。但你必须知道你正在调用一个基于64位的库。
可移植性是一个备受关注的目标,但很少有人真正实现它。经过30多年的系统级,固件和应用程序编程,我认为它更像是幻想与目标。不幸的是,硬件迫使我们优化硬件。因此,当我编写库时,我使用以下内容:
对所有函数调用使用指向结构的指针进行输入和输出:
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);
}
总之,完全可移植的解决方案是不可行的。即使你从完全可移植性开始,最终你也必须偏离。这是事情真正变得混乱的时候。你会因为偏差而破坏你的风格,这会破坏你的文档并使用户感到困惑。我认为最好从一开始就计划好它。
硬件总会让您产生偏差。只考虑'endianness'导致多少麻烦 - 更不用说每天交换字节顺序所使用的CPU周期数。
答案 1 :(得分: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几乎没有任何东西可以接近。微软的编译器在每个版本中打破了C ++ ABI。 GCC努力维护不同版本的ABI兼容性,并使用已发布的Itanium ABI(具有讽刺意味的是现在已经死的架构)。其他编译器可能会做自己的,完全不同的事情。 (然后你当然遇到了C ++标准库实现的问题,例如你的string
是否包含一个,两个或三个指针,以及它们的顺序是什么?)
总结:编译器的ABI的许多方面,尤其是与C有关的,都由硬件架构决定。只要正确传达函数调用约定等某些方面,相同硬件的不同C编译器就应该生成兼容的二进制代码。但是,对于更高级别的语言,所有投注都是关闭的,并且两个不同的编译器是否可以生成可互操作的代码必须根据具体情况来决定。
答案 2 :(得分:1)
如果我理解你的需求是正确的,那么uint风格的那些只会给你二进制兼容性保证和原因int,char会但其他人往往会有所不同。即long
在Windows和Linux上,Windows将其视为4byte,将Linux视为8byte。如果您真的依赖于ABI,则必须规划您要提供的平台,并且可以使用typedef
来使事物标准化和可读。