如何编写完全可移植的 4 字节字符常量的编译时初始化
How to write a compile-time initialisation of a 4 byte character constant that is fully portable
(遗留)代码大致如下所示。
#define MAKEID(a,b,c,d) (((UInt32)a)<<24 | ((UInt32)b)<<16
| ((UInt32)c)<<8 | ((UInt32)d) )
#define ID_FORM MAKEID('F','O','R','M')
//...
struct S {
int id;
int x;
double d;
// other stuff
};
//...
S s;
socket.read(&s, sizeof(s)); // read network data over struct
if (s.id == ID_FORM) { }
该代码将字符流读入结构。已知 S.id 是一个 4 个字符的常量,例如流(网络)顺序中的"FORM"或"DATA",它决定了结构其余部分的布局。所有比较都是使用预定义常量的整数。
MAKEID 宏是大端序的,因为它将第一个(字符)参数放在最高有效字节处,这也是最低内存地址。宏的小端版本如下所示,将第一个(字符)参数放在最低有效字节处,现在是最低内存地址。
#define MAKEID(a,b,c,d) (((UInt32)d)<<24 | ((UInt32)c)<<16
| ((UInt32)b)<<8 | ((UInt32)a) )
问题是如何重写它,使其在大端和小端架构上同样有效。
不,我不想同时编写两个宏并选择带有 #ifdef 的宏。代码中没有其他字节序依赖项,我不想在这里介绍一个。便携是必经之路。
不,我不想写函数。此常量用于函数无法到达的地方。我编写了一个可移植函数来初始化联合,但代码无法编译。
我正在寻找任何类型的可移植宏或模板来进行编译时初始化。
在回答注释时,这是真正的代码。它是网络协议的一部分,在大多数情况下,另一端负责字节序。这恰好是一个例外,其中另一端以网络字节顺序生成,并且此端在历史上被写为大端序作为 4 字节字符常量,如"FORM"。我需要一个点解决方案,而不是将字节序的概念传播到代码中的其他地方的解决方案。
您的MAKEID
宏与字节序无关。它在大端和小端系统上的工作方式相同。
宏可能看起来是特定于大端序的,但C++中的移位和按位或运算都是根据它们对所操作值的结果定义的,而不是根据这些值的基础存储来定义的。
执行42 << 24
可以保证将值42
放在结果的最高有效 8 位中,无论字节顺序如何。对于按位或操作也是如此。这意味着MAKEID(0x12, 0x34, 0x56, 0x78)
的结果总是0x12345678
的,无论底层存储的字节顺序如何。
如果要生成一个整数,其基础存储始终具有相同的位模式(例如,0x12、0x34、0x56、0x78),那么您确实必须重新考虑您的方法。这样的整数在大端系统上的值0x12345678
,在小端系统上0x78563412
,在中端系统上可能0x56781234
。
但是,如果该位模式是通过使用特定字节顺序(例如大端序/网络字节顺序)定义的通信接口接收的,则如果希望接收系统正确解释这些值,并且包括四字节 ID 值,则必须将接收的任何多字节值转换为系统的本机字节顺序。
这就是为什么我在答案的早期版本中说,如果您发现在某些系统上(特别是那些系统的字节顺序与通信的字节顺序不匹配的系统),从流中读取的 ID 与MAKEID
的结果不匹配,那么可能的罪魁祸首是反序列化代码。(反)序列化代码是考虑字节序的最重要位置。例如,在收到的字节上覆盖您期望的结构很容易,但如果可能存在字节顺序不匹配或填充差异,则这是错误的解决方案。
我向为我工作的程序员设置了同样的问题。在我们之间,我们提出了以下4个解决方案。我们将使用宏,也许在时间允许的情况下转换代码以使用其中一个函数。
unsigned int MakeId(char a, char b, char c, char d) {
char x[4] = { a, b, c, d };
return *(int*)x;
}
unsigned int MakeId(char a, char b, char c, char d) {
union {
char x[4];
int i;
} u = { a, b, c, d };
return u.i;
}
unsigned int MakeId(const char* s) { return *(int*)s; }
#define MAKEID(s) *(int*)(s);
#define FORM_ID MAKEID("Form")
在这种情况下,Stack Overflow的强大头脑没有实现。
与其在不同的机器上以不同的方式定义常量,不如处理收到的数据:
if (ntohl(s.id) == ID_FORM) { }
编辑: 为了避免编辑代码,您可以使用 htonl 来初始化ID_FORM:
#define ID_FORM htonl(MAKEID('F','O','R','M'))
这依赖于htonl
是一个宏。通常都是这样。如果是这样,它通常使用您试图避免的相同条件来定义:http://www.jbox.dk/sanos/source/include/net/inet.h.html(例如)。
因此,如果您的系统上htonl
不是宏,那么我看到的唯一理智的选择就是实际坚持#ifdef
.
请记住,从现在开始,您的ID_FORM现在处于"网络字节序",而不是"主机字节序"。
- 多字符常量警告
- const_cast<字符 *>(字符* 常量) 不是左值?
- 常量字符数组模板与字符常量* 函数重载
- 如何检查字符常量是否符合ASCII
- 从字符串数组转换为字符* 常量
- 字符常量或字符串常量
- 字符常量数组为空,即使已为其赋值 [C++]
- 如何创建 ' 字符常量
- 如何编写完全可移植的 4 字节字符常量的编译时初始化
- 我得到以下错误:[警告]多字符字符常量
- C++错误:警告:多字符字符常量/a用于用法
- 字符常量和函数名称的 sizeof() 背后的逻辑
- 如何在 C 中将多字符常量转换为整数
- C++ wifstream:不兼容的字符常量*,wchar_t康斯特*类型
- 不允许从"常量无符号字符*常量*"到"常量字符*常量*"static_cast
- 修改字符 *常量字符串
- 为什么字符常量/文字不能为空?
- C++字符 * 常量与字符 *,为什么有时一个有时另一个
- 警告多字符字符常量 [-Wmultichar]
- 开关语句多字符常量