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

dav*_*pfx 3 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) { }
Run Code Online (Sandbox Code Playgroud)

代码将字符流读入结构中.已知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) )
Run Code Online (Sandbox Code Playgroud)

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

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

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

任何类型的可移植宏或模板进行编译时初始化都是我正在寻找的.

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

Bar*_*nau 7

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

宏可能看起来是特定于大端的,但C++中的移位和按位或操作都是根据它们对操作的值的结果来定义的,而不是这些值的底层存储.
这样做42 << 24是保证把值42在结果的最显著8位,无论字节顺序.类似地用于按位或操作.这意味着无论底层存储的字节顺序如何,结果MAKEID(0x12, 0x34, 0x56, 0x78)总是如此0x12345678.

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

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

  • @ david.pfx:覆盖网络数据和struct*是一种反序列化的形式.它是最脆弱的类型,网络流和接收系统之间的字节顺序差异是可以打破它的方法之一. (6认同)
  • @ david.pfx您的序列化是特定于endian的代码.有序列化代码:在字节和你执行的结构之间重新解释.IMO这种序列化是不好的形式,应该避免. (3认同)
  • @ david.pfx:在这种情况下,请更新您的问题,并解释为什么您需要此常量的字节序变体以及为什么数据中的其他多字节字段没有问题. (2认同)

dav*_*pfx 2

我向为我工作的程序员提出了同样的问题。我们共同提出了以下 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")
Run Code Online (Sandbox Code Playgroud)

这一次,Stack Overflow 的强大头脑并没有发挥作用。

  • 现在我明白了 - 您需要一个 4 字节常量,其中包含按网络顺序排列的字节“F”、“o”、“r”和“m”。这是另一个解决方案: `#define FORM_ID (*(int *) (char[]) {'F', 'o', 'r', 'm'})` 它看起来不如 `*(int *) 好“Form”`,但它没有额外的零字节。 (6认同)