将大端结构转换为小端

5

我正在制作一个与使用大端字节序的患者监视器通信的C程序。例如,如果我有特定的C结构体

typedef struct {
   int short a;
   int short b;
   int       c;
} msg;

要读取这种结构,我可以简单地使用ntohs(msg.a)、ntohs(msg.b)、ntohl(msg.c)。但有些结构具有一个短整型的缓冲区,但该缓冲区本身是另一种结构的类型。例如:

typedef struct {
   int short length;
   int short b[MAX_BUF_SIZE];
} msg1;

上述结构中的字段“b”表示另一个结构,如下所示:
typedef struct {
   int short a;
   int short b;
} msg2;

现在,我的问题是1)我应该将结构体“msg1”的所有短整型转换为主机顺序,然后将其强制转换为类型“msg2”的指针,并简单地读取“msg2.a”和“msg2.b”,还是2)我也应该转换“msg2.a”和“msg2.b”的字节顺序,或者3)只需将“msg1.b”强制转换为类型“msg2”的指针,并通过将每个值转换为主机顺序来读取“msg2.a”和“msg2.b”?
请告诉我正确的读取msg1的方法。

方法1

int t[msg1.length];
for(int i = 0; i < msg1.length; i++)
   t[i] = ntohs(*(msg1.b + i));
msg2 * msg2_m = (msg2 *)t;
/* should I convert the msg2_m.a and msg2_m.b as well? */
printf("%d:%d", msg2_m.a, msg2_m.b);

方法2

与之前的方法相同,唯一不同之处在于:

printf("%d:%d", ntohs(msg2_m.a), ntohs(msg2_m.b));

方法三

不将 "msg1.b" 转换,直接将 "msg1.b" 强制转换为 "msg2",只将 "msg2.a" 和 "msg2.b" 转换为主机字节顺序。

msg2 *msg2_m = (msg2 *)msg1.a;
printf("%d:%d", ntohs(msg2_m.a), ntohs(msg2_m.b));

我需要了解当一个结构体被转换成其他结构体时,它的字节顺序是否会根据新结构体在网络上传输而改变?我认为“方法3”是正确的,但这只是我的个人看法,我不确定字节顺序的内部情况。任何帮助将不胜感激。
谢谢, Shivam Kalra

正确(Correct)是发送者(sender)正在做的事情的相反。那么你在那边(over there)在做什么呢? - Jon
2个回答

3

首先,类型转换不会影响字节顺序。

其次,在代码的各个地方都考虑字节顺序是不可取的,因为你或其他人 一定会 忘记,在某个地方犯下错误,并且在以后试图找到错误时将会非常困难。因此,一旦读取数据,请立即将其转换为正确的字节顺序。如果读取一个包含short数组的结构体,请立即将整个short数组转换为正确的字节顺序。对于任何其他结构体也同样如此。转换数据并存储结果;不要每次需要读取或打印内容时都使用ntohs。将此代码与程序的其他部分隔离开,这样你就可以忘记字节顺序,并且只在处理转换代码时考虑字节顺序。


谢谢回复。在不使用ntoh..函数的情况下,有哪些改变字节序的方法? - Shivam
@ShivamKalra:你在使用哪个平台?大多数平台都有特定的宏或函数来帮助处理这个问题。而且,除非你真的在处理网络协议,否则在概念上不应该使用NTOHL系列。 - Ben Zotto
swab() 是其中一种方法。但我并不是想阻止你使用 ntoh,我只是想强调重要性:进行一次转换并存储结果,而不是在代码中到处添加转换调用。ntoh 允许您的代码继续工作,即使它被重新编译以在大端硬件上运行;这是一件好事。 - Kyle Jones

2

我认为通常最好不要直接尝试读取或写入 struct。相反,你应该明确地定义你的数据传输格式,并逐字节地将 struct 写入其中(然后在读取时执行反向操作)。例如,要进行写入:

typedef struct {
   short int a;
   short int b;
   int       c;
} msg;

你可以做:

void WriteBigEndian16(uint16_t x, FILE* fp)
{
   fputc((x >> 8) & 0xFF, fp);
   fputc( x       & 0xFF, fp);
}

void WriteBigEndian32(uint32_t x, FILE* fp)
{
   fputc((x >> 24) & 0xFF, fp);
   fputc((x >> 16) & 0xFF, fp);
   fputc((x >>  8) & 0xFF, fp);
   fputc( x        & 0xFF, fp);
}

FILE* fp = fopen(...);
msg m; // Assume that this is initialized.
WriteBigEndian16(m.a, fp);
WriteBigEndian16(m.b, fp);
WriteBigEndian32(m.c, fp);

这样做的优点有:
  • 代码与大小端无关。无论您在小端机器还是大端机器上运行,都不需要了解。
  • 避免任何有关内存中 struct 的字节顺序是否交换的混淆。它们将始终是平台本地顺序。
  • 您也定义了字段的大小。如果您不定义您的线路格式,不仅可能存在大小端不匹配的情况,还可能存在大小不匹配的情况。谁说两个端点都使用相同的 int 大小?

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接