如何编写完全可移植的4字节字符常量的编译时初始化

时间:2014-02-23 06:19:28

标签: c++ cross-platform

(传统)代码看起来大致如此。

#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宏是big-endian,因为它将第一个(字符)参数放在最高有效字节,这也是最低内存地址。宏的小端语版本看起来像这样,将第一个(字符)参数放在最低有效字节,现在是最低内存地址。

 #define MAKEID(a,b,c,d) (((UInt32)d)<<24 | ((UInt32)c)<<16 
                        | ((UInt32)b)<<8 | ((UInt32)a) )

问题是如何重写它以便它在big-endian和little-endian架构上同样有效。

不,我不想写两个宏并选择带#ifdef的宏。代码中的任何地方都没有其他的endian依赖,我不想在这里介绍一个。便携式是要走的路。

不,我不想写一个函数。此常量用于函数无法执行的位置。我编写了一个初始化union的可移植函数,代码无法编译。

我正在寻找任何类型的可编程时初始化的可移植宏或模板。

在回答评论时,这是真正的代码。它是网络协议的一部分,另一端在大多数情况下负责字节序。这恰好是一个异常,其中另一端以网络字节顺序生成,并且这一端在历史上被写为big-endian,如4字节字符常量,如'FORM'。我需要一个单点解决方案而不是将字符串主义的概念传播到代码中的其他地方。

3 个答案:

答案 0 :(得分:7)

您的MAKEID宏与字节顺序无关。它在大端和小端系统上的工作方式相同。

宏可能看起来是特定于大端的,但C ++中的移位和按位或操作都是根据它们对操作的值的结果来定义的,而不是那些值的底层存储。登记/> 无论字节顺序如何,保证42 << 24保持将值42置于结果的最高8位。类似地用于按位或操作。这意味着MAKEID(0x12, 0x34, 0x56, 0x78)的结果始终为0x12345678,无论底层存储的字节顺序如何。

如果要生成一个整数,其底层存储总是具有相同的位模式(例如,0x12,0x34,0x56,0x78),那么你真的必须重新考虑你的方法。这样的整数在big-endian系统上的值为0x12345678,在little-endian系统上为0x78563412,在中端系统上可能为0x56781234。 但是,如果通过使用特定字节顺序定义的通信接口(例如big-endian / network字节顺序)接收到该位模式,则必须将接收到的任何多字节值转换为系统的本机字节如果您希望接收系统正确解释这些值并且包含四字节ID值,请单击。

这就是为什么我在答案的早期版本中说过,如果你在某些系统上发现(特别是系统的字节顺序与通信的字节顺序不匹配的那些系统),从流中读取的ID不会匹配MAKEID的结果,那么可能的罪魁祸首就是反序列化代码。 (de)序列化代码是考虑字节序的最重要的地方。例如,将您期望的结构覆盖在您收到的字节上很容易,但如果可能存在字节顺序不匹配或填充差异,那么这是错误的解决方案。

答案 1 :(得分:1)

我将同样的问题提交给为我工作的程序员。在我们之间,我们提出了以下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的强大思想无法实现。

答案 2 :(得分:0)

您应该处理收到的数据,而不是在不同的机器上定义不同的常量:

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现在处于“网络字节序”,而非“主机字节序”。