【问题标题】:Reverse the Endianness of a C structure反转 C 结构的字节顺序
【发布时间】:2013-09-23 14:09:00
【问题描述】:

我在 C 中有一个如下所示的结构:

typedef u_int8_t NN;
typedef u_int8_t X;
typedef int16_t S;
typedef u_int16_t U;
typedef char C;

typedef struct{
 X test;
 NN test2[2];
 C test3[4];
 U test4;
} Test;

我已将结构和写入字段的值声明如下:

Test t;
int t_buflen = sizeof(t);
memset( &t, 0, t_buflen);
t.test = 0xde;
t.test2[0]=0xad; t.test2[1]=0x00;
t.test3[0]=0xbe; t.test3[1]=0xef; t.test3[2]=0x00; t.test3[3]=0xde;
t.test4=0xdeca; 

我通过 UDP 将此结构发送到服务器。目前,当我在本地测试时这工作正常,但是我现在需要将此结构从我的 little-endian 机器发送到 big-endian 机器。我不太确定该怎么做。

我已经研究过使用 htons,但我不确定这是否适用于这种情况,因为如果我理解正确的话,它似乎只为 16 位或 32 位的 unsigned ints 定义。

【问题讨论】:

  • 鉴于 viraptor 的响应(如下)解决了该方法,但您仍然需要机制,这是关于结构和字节序问题的好帖子... here.

标签: c endianness


【解决方案1】:

我认为这里可能存在两个问题,具体取决于您通过 TCP 发送此数据的方式。

问题 1:字节序

正如你所说的字节顺序是一个问题。当您提到使用 htonsntohs 短裤时,您是对的。您可能还会发现htonl 和它的反面也很有用。

字节顺序与内存中多字节数据类型的字节顺序有关。因此,对于单字节宽度的数据类型,您不必担心。在您的情况下,我猜您正在质疑的是 2 字节数据。

要使用这些功能,您需要执行以下操作...

Sender:
-------
t.test     = 0xde; // Does not need to be swapped
t.test2[0] = 0xad; ... // Does not need to be swapped
t.test3[0] = 0xbe; ... // Does not need to be swapped
t.test4    = htons(0xdeca); // Needs to be swapped 

...

sendto(..., &t, ...);


Receiver:
---------
recvfrom(..., &t, ...);
t.test4    = ntohs(0xdeca); // Needs to be swapped 

使用htons()ntohs() 使用以太网字节排序...大端。因此,您的小端机器字节交换 t.test4 并且在接收时大端机器只使用读取的值(ntohs() 是一个有效的 noop)。

下面的图表将使这一点更清楚......

如果您不想使用htons() 函数及其变体,那么您可以只在字节级别定义缓冲区格式。这张图让这个更清楚......

在这种情况下,您的代码可能类似于

Sender:
-------
uint8_t buffer[SOME SIZE];
t.test     = 0xde;
t.test2[0] = 0xad; ... 
t.test3[0] = 0xbe; ... 
t.test4    = 0xdeca;

buffer[0] = t.test;
buffer[1] = t.test2[0];
/// and so on, until...
buffer[7] = t.test4 & 0xff;
buffer[8] = (t.test4 >> 8) & 0xff;    

...

sendto(..., buffer, ...);

Receiver:
---------
uint8_t buffer[SOME SIZE];
recvfrom(..., buffer, ...);

t.test     = buffer[0];
t.test2[0] = buffer[1];
// and so on, until...
t.test4    = buffer[7] | (buffer[8] << 8);

无论发送者和接收者各自的字节顺序如何,发送和接收代码都将起作用,因为缓冲区的字节布局是由运行在两台机器上的程序定义和知道的。

但是,如果您以这种方式通过套接字发送结构,则还应注意以下警告...

问题 2:数据对齐

"Data alignment: Straighten up and fly right" 这篇文章非常适合阅读这篇文章...

您可能遇到的另一个问题是数据对齐。情况并非总是如此,即使在使用不同字节序约定的机器之间也是如此,但仍然需要注意......

struct
{
    uint8_t  v1;
    uint16_t v2; 
}

在上面的代码中,v2 从结构开始的偏移量可以是 1 字节、2 字节、4 字节(或几乎任何东西)。编译器无法对结构中的成员重新排序,但它可以填充变量之间的距离。

假设机器 1 有一个 16 位宽的数据总线。如果我们在没有填充的情况下采用结构,机器将必须执行两次获取才能获得v2。为什么?因为我们在硬件级别一次访问 2 个字节的内存。因此编译器可以像这样填充结构

struct
{
    uint8_t  v1;
    uint8_t  invisible_padding_created_by_compiler;
    uint16_t v2; 
}

如果发送方和接收方在将数据打包到结构中的方式不同,那么仅将结构作为二进制 blob 发送就会给您带来问题。在这种情况下,您可能必须在发送之前手动将变量打包到字节流/缓冲区中。这通常是最安全的方式。

【讨论】:

  • 为了处理对齐问题,大多数编译器也有一些指定对齐的机制,特别是指定packed对齐(即无填充)。这可能会对性能产生一些轻微/微妙的影响,但我不会太担心。
  • @SchighSchagh:是的,这是一个好点 +1!请注意,这可能会引入可移植性问题......
  • 感谢您提供非常详细和清晰的回答。我不清楚字节中位的实际顺序是否必须交换,或者只是字节,感谢您清除它。至于您指出的数据对齐问题,这是我在早期开发代码时遇到的问题,并且能够使用#pragma pack(1) 解决
  • 很高兴为您提供帮助。谢天谢地,它只是字节......在 BE 和 LE 上,字节内的位顺序是相同的。
【解决方案2】:

确实没有结构的字节序。所有单独的字段都需要在需要时转换为大端序。您可以复制结构并使用hton/htons 重写每个字段,然后发送结果。 8位字段当然不需要任何修改。

如果是 TCP,您也可以单独发送每个部分并依靠 nagle 算法将所有部分合并到一个数据包中,但是对于 UDP,您需要预先准备好所有内容。

【讨论】:

    【解决方案3】:

    无论涉及的机器的字节顺序如何,您通过网络发送的数据都应该是相同的。你需要研究的关键词是序列化。这意味着将数据结构转换为一系列位/字节,以通过网络发送或保存到磁盘,无论架构或编译器如何,这些都将始终相同。

    【讨论】:

      猜你喜欢
      • 2020-09-09
      • 1970-01-01
      • 1970-01-01
      • 2016-02-01
      • 1970-01-01
      • 2021-11-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多