我目前正在尝试创建 C 源代码,无论目标系统的字节顺序如何,都能正确处理I / O.
我选择了“little endian”作为我的I / O约定,这意味着,对于大端CPU,我需要在写入或读取时转换数据。
转换不是问题。我面临的问题是检测字节顺序,最好是在编译时(因为CPU在执行过程中不会改变字节顺序......)。
到目前为止,我一直在使用它:
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
...
#else
...
#endif
它被记录为GCC预定义宏,而Visual似乎也理解它。
但是,我收到报告称某些big_endian系统(PowerPC)检查失败。
所以,我正在寻找一个万无一失的解决方案,它可以确保无论编译器和目标系统如何都能正确检测到字节顺序。好吧,他们中的大多数至少......
[编辑]:提出的大多数解决方案都依赖于“运行时测试”。编译期间编译器有时可以正确评估这些测试,因此不会产生实际的运行时性能。
然而,用某种<<<<< if (0) { ... } else { ... }
>>是不足够的。在当前的代码实现中,变量和函数声明依赖于big_endian检测。使用if语句无法更改这些内容。
嗯,显然,有一个后退计划,即重写代码......
我宁愿避免这种情况,但是,它看起来似乎正在减少希望......
[编辑2]:我通过深度修改代码测试了“运行时测试”。尽管他们正确地完成了工作,但这些测试也会影响性能。
我期待这样,因为测试具有可预测的输出,编译器可以消除坏分支。但不幸的是,它并不是一直有效。 MSVC是一个很好的编译器,并且成功地消除了坏的分支,但是GCC的结果是混合的,这取决于版本,测试类型,并且对64位比对32位的影响更大。
很奇怪。这也意味着编译器无法确保运行时测试。
编辑3 :这些天,我正在使用编译时常量联合,期望编译器将其解析为明确的是/否信号。 而且效果很好: https://godbolt.org/g/DAafKo
答案 0 :(得分:14)
为什么不使用big-endian命令(多被认为是"network order")而不是查找编译时检查,而是使用大多数UNIX系统提供的htons
/htonl
/ntohs
/ntohl
函数和视窗。他们已经被定义为完成你想要做的工作。为什么重新发明轮子?
答案 1 :(得分:13)
如前所述,检测Big Endian的唯一“真正”方法是使用运行时测试。
但是,有时候,宏可能更受欢迎。
不幸的是,我没有找到一个“测试”来检测这种情况,而是检测它们的集合。
例如,GCC建议:__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
。但是,这仅适用于最新版本,早期版本(和其他编译器)将为此测试提供错误值“true”,因为NULL == NULL。所以你需要更完整的版本:defined(__BYTE_ORDER__)&&(__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)
好的,现在这适用于最新的GCC,但其他编译器呢?
您可以尝试通常在大端编译器上定义的__BIG_ENDIAN__
或__BIG_ENDIAN
或_BIG_ENDIAN
。
这将改善检测。但是,如果您专门针对PowerPC平台,则可以添加一些测试以改进更多检测。试试_ARCH_PPC
或__PPC__
或__PPC
或PPC
或__powerpc__
或__powerpc
甚至powerpc
。将所有这些定义绑定在一起,您可以非常公平地检测大端系统,特别是powerpc,无论编译器及其版本如何。
因此,总而言之,没有“标准的预定义宏”可以保证在所有平台和编译器上检测大端CPU,但是有许多这样的预定义宏,它们共同给出了在大多数情况下正确检测大端的可能性很高。
答案 2 :(得分:11)
在C语言的编译时,除了信任预处理器#define
之外,你做的不多,而且没有标准的解决方案,因为C标准不关心字节序。
但是,您可以添加一个在程序开始时在运行时完成的断言,以确保在编译时完成的假设为真:
inline int IsBigEndian()
{
int i=1;
return ! *((char *)&i);
}
/* ... */
#ifdef COMPILED_FOR_BIG_ENDIAN
assert(IsBigEndian());
#elif COMPILED_FOR_LITTLE_ENDIAN
assert(!IsBigEndian());
#else
#error "No endianness macro defined"
#endif
(其中COMPILED_FOR_BIG_ENDIAN
和COMPILED_FOR_LITTLE_ENDIAN
之前根据您的预处理程序字节顺序检查是#define
d)
答案 3 :(得分:6)
尝试类似:
if(*(char *)(int[]){1}) {
/* little endian code */
} else {
/* big endian code */
}
并查看编译器是否在编译时解析它。如果没有,你可能会更好运与联盟做同样的事情。实际上我喜欢使用评估为0,1或1,0(分别)的联合来定义宏,这样我就可以做一些事情,比如访问buf[HI]
和buf[LO]
。
答案 4 :(得分:4)
尽管有编译器定义的宏,但我认为没有一种编译时方法可以检测到这种情况,因为确定架构的字节序包涉及分析它在内存中存储数据的方式。
这是一个能够做到这一点的功能:
bool IsLittleEndian () {
int i=1;
return (int)*((unsigned char *)&i)==1;
}
答案 5 :(得分:3)
正如其他人所指出的那样,在编译时没有一种可移植的方法来检查字节序。但是,一种选择是使用autoconf
工具作为构建脚本的一部分来检测系统是big-endian还是little-endian,然后使用AC_C_BIGENDIAN
宏来保存这些信息。从某种意义上说,这构建了一个程序,可以在运行时检测系统是big-endian还是little-endian,然后具有该程序输出信息,然后可以由主源代码静态使用。
希望这有帮助!
答案 6 :(得分:2)
这来自p。 Pointers in C中的45个:
#include <stdio.h>
#define BIG_ENDIAN 0
#define LITTLE_ENDIAN 1
int endian()
{
short int word = 0x0001;
char *byte = (char *) &word;
return (byte[0] ? LITTLE_ENDIAN : BIG_ENDIAN);
}
int main(int argc, char* argv[])
{
int value;
value = endian();
if (value == 1)
printf("The machine is Little Endian\n");
else
printf("The machine is Big Endian\n");
return 0;
}
答案 7 :(得分:1)
您无法在编译时检测到它可以在所有编译器中移植。也许你可以在运行时更改代码来实现它 - 这是可以实现的。
答案 8 :(得分:1)
使用预处理程序指令无法在C中可移植地检测字节顺序。
答案 9 :(得分:1)
套接字的ntohl
函数可用于此目的。 Source
// Soner
#include <stdio.h>
#include <arpa/inet.h>
int main() {
if (ntohl(0x12345678) == 0x12345678) {
printf("big-endian\n");
} else if (ntohl(0x12345678) == 0x78563412) {
printf("little-endian\n");
} else {
printf("(stupid)-middle-endian\n");
}
return 0;
}
答案 10 :(得分:1)
我的 GCC 版本是 9.3.0,配置为支持 powerpc64 平台,我已经测试并验证它支持以下宏逻辑:
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
......
#endif
#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
.....
#endif
答案 11 :(得分:0)
我自由地重新格式化了引用的文本
自2017年7月18日起,我使用
union { unsigned u; unsigned char c[4]; }
如果sizeof (unsigned) != 4
,您的测试可能会失败。
最好使用
union { unsigned u; unsigned char c[sizeof (unsigned)]; }
答案 12 :(得分:0)
正如大多数人提到的那样,编译时间是最好的选择。假设您不进行交叉编译,而是使用cmake
(当然,它也可以与其他工具(例如configure
脚本一起使用),那么您可以使用预测试,它是已编译的.c或.cpp文件,它可以为您提供所运行处理器的实际验证字节序。
通过cmake
,您可以使用TestBigEndian
宏。它设置一个变量,然后可以将其传递到软件。像这样(未经测试):
TestBigEndian(IS_BIG_ENDIAN)
...
set(CFLAGS ${CFLAGS} -DIS_BIG_ENDIAN=${IS_BIG_ENDIAN}) // C
set(CXXFLAGS ${CXXFLAGS} -DIS_BIG_ENDIAN=${IS_BIG_ENDIAN}) // C++
然后在您的C / C ++代码中,您可以检查IS_BIG_ENDIAN
是否定义:
#if IS_BIG_ENDIAN
...do big endian stuff here...
#else
...do little endian stuff here...
#endif
因此,这种测试的主要问题是交叉编译,因为您可能处于完全不同的CPU上,并且字节序不同。但是至少在编译其余代码时,它可以为您提供字节序,并且可以正常工作对于大多数项目。
答案 13 :(得分:-1)
我知道我参加这个派对已经很晚了,但这是我的看法。
int is_big_endian() {
return 1 & *(uint16_t*)"01";
}
这是基于'0'
为十进制48和'1'
49这一事实,因此'1'
设置了LSB位,而'0'
则没有。我可以制作'\x00'
和'\x01'
,但我认为我的版本更具可读性。
答案 14 :(得分:-5)
#define BIG_ENDIAN ((1 >> 1 == 0) ? 0 : 1)