C中的endianess检测和性能
我有一个性能关键的C代码需要在各种平台上工作。 其中一些是小端,另一些是大端。
基于宏观检测,检测字节序目前是不完美的过程。 但很难确定宏检测是否适用于系统和编译器的所有组合。 欢迎来到便携式代码世界。
检测endianess的一种相对安全的方法是使用运行时测试,并希望它将被编译器优化。 这些方面的东西:
static const int one = 1; #define IS_LITTLE_ENDIAN (*(char*)(&one))
一般来说它有效。 编译器应正确检测到该宏的结果对于给定的体系结构总是相同的(1表示小端,0表示大端),只需完全删除内存访问和关联的分支。
我的问题是:总是如此吗? 我们是否可以期望编译器始终正确理解此测试,并始终正确地优化它? (假设-O2 / -O3或等效的优化级别,当然不适用于调试代码)
我特别担心双端CPU ,比如ARM。 由于这样的CPU可以是大端或小端,取决于OS参数,编译器可能难以“硬连线”这样的端序测试。 另一方面,我不希望应用程序以“任一端选择模式”工作:我想它应该编译为一个精确和明确的endianess。 因此,IS_LITTLE_ENDIAN应始终保持相同。
无论如何,我要求遇到遇到这种情况的人的经历。 由于我目前没有双端CPU和编译器,我无法测试并观察上述假设。
[ 编辑 ] @Brandin建议“保存宏的结果”,使其成为一个变量。 我猜他提出这样的事情:
static const int one = 1; static const int isLittleEndian = *(char*)(&one);
由于在编译时会计算静态const int,因此确实可以保证编译器必须知道isLittleEndian的值,因此可以正确地优化使用此变量的分支。
不幸的是,它不起作用。 上述声明导致以下编译错误:
error: initializer element is not constant
我想这是因为在编译时无法评估一个(指针地址)。
@ HuStmpHrrr的变体,使用union,看起来更好:没有要评估的指针地址。 不幸的是,它不能更好地工作,并导致相同的编译错误。
我想这是因为编译器认为联合不够简单,不能用作静态const初始化的值。
所以我们回到了开头,用宏来代替。
同样的想法,但不同的技巧。 这段代码也可以。
union { int num; char[sizeof(int)] bytes; } endian; endian.num = 1;
然后使用endian.bytes[0]
来判断。
这样,事情变得更加自然,编译器应该做一些事情,因为这很容易被简单的数据流优化器实现跟踪。
endian.bytes[0]
应该缩小为常数。
无论如何,这种方式依赖于编译器。
我忽略了@OliCharlesworth在评论中链接的post中的小问题,我同意它的精神:你确定字节顺序真的很重要,如果确实如此,为什么呢?
-
如果实际上并不重要,便携式且可能非常快速的解决方案是使用
memcpy
,它将以主机字节顺序复制字节。 这意味着大端机器和小端机器之间的文件不可互换。 -
如果需要,您可以通过对值执行位移操作(如上所述)将整数转换为特定的字节顺序,而不管字节顺序如何。 例如,将
(value >> 8) & 0xff
后跟(value >> 0) & 0xff
写入文件而不是原始值本身将始终以big endian顺序写入16位值,并且您将始终读取首先是16位值的高8位(最高有效字节)。 这基本上是像htonl
那样的函数,除了函数返回的值可能由于其基础字节表示的重构的可能性而不同,这与简单地逐个写入移位字节不同。
除了“我有性能关键代码”之外没有更多上下文,任何其他指导都不可用。 我已经提到的主要问题是机器的字节顺序对于这个问题如此重要的原因。
您可以使用htonl来检测它:
#include int main() { if (htonl(1) == 1) printf("big endian\n"); else printf("little endian\n"); }