【问题标题】:Is reading one byte at a time endianness agnostic regardless of value size?无论值大小如何,一次读取一个字节是否与字节顺序无关?
【发布时间】:2019-05-31 07:01:14
【问题描述】:

假设我正在从流中读取和写入 uint32_t 值。如果我一次向/从流中读取/写入一个字节并像下面的示例一样移动每个字节,无论机器字节序如何,结果都会保持一致吗?

在此处的示例中,流是内存中称为p 的缓冲区。

static uint32_t s_read_uint32(uint8_t** p)
{
    uint32_t value;
    value  = (*p)[0];
    value |= (((uint32_t)((*p)[1])) << 8);
    value |= (((uint32_t)((*p)[2])) << 16);
    value |= (((uint32_t)((*p)[3])) << 24);
    *p += 4;
    return value;
}

static void s_write_uint32(uint8_t** p, uint32_t value)
{
    (*p)[0] = value & 0xFF;
    (*p)[1] = (value >> 8 ) & 0xFF;
    (*p)[2] = (value >> 16) & 0xFF;
    (*p)[3] = value >> 24;
    *p += 4;
}

我目前无法访问大端机器来测试这一点,但我的想法是,如果每个字节一次写入一个,则每个单独的字节都可以从流中独立写入或读取。然后 CPU 可以通过将这些细节隐藏在移位操作后面来处理字节顺序。这是真的吗,如果不是,请任何人解释为什么不?

【问题讨论】:

  • 你的直觉是正确的。
  • 是吗?在我看来,(*p)[0] 指的是小端机器上的最低有效字节,但指的是大端机器上的最高有效字节。
  • @500-InternalServerError 这很好。由于他p 的值读取为32 位整数,因此如果将值读取为32,则实现将其视为最低有效字节还是最高有效字节都没有关系-位整数。
  • 32 位值的第 0...7 位始终包含其值的 8 个最小 sig 位。字节序是指这 32 位是如何存储在内存中的。
  • 是的,一次读取一个 bytebyte-order 不可知的。但是,您的 代码 不是字节顺序不可知的,整数的字节以严格的小端顺序存储和读取。

标签: c endianness


【解决方案1】:

如果我一次向/从流中读取/写入一个字节并像下面的示例一样移动每个字节,无论机器字节序如何,结果都会保持一致吗?

是的。您的 s_write_uint32() 函数按从最低有效到最高的顺序存储输入值的字节,而不管它们在该值的本机表示中的顺序如何。无论uint32_t 的底层表示如何,您的s_read_uint32() 都会正确地反转此过程。这些工作是因为

  • 移位运算符(&lt;&lt;&gt;&gt;)的行为是根据左操作数的定义的,而不是它的表示
  • &amp; 0xff 屏蔽了左操作数的所有位,但其最低有效字节的那些位,无论值的表示形式如何(因为 0xff 具有匹配的表示形式),并且
  • |= 操作只是将字节放入结果中;通过前面的左移适当地选择位置。如果改用+=,这可能会更清楚,但结果不会有什么不同。

但是请注意,在某种程度上,您正在重新发明轮子。 POSIX 定义了一个函数对htonl()nothl()——在许多非POSIX 系统上也支持——用于处理四字节数字中的字节顺序问题。这个想法是,在发送时,每个人都使用htonl() 将主机字节顺序(无论是什么)转换为网络字节顺序(大端),然后发送生成的四字节缓冲区。收到后,每个人都接受四个字节为一个数字,然后使用ntohl() 将网络字节序转换为主机字节序。

【讨论】:

    【解决方案2】:

    它可以工作,但 memcpy 后跟一个条件字节交换 will give you much better codegen 用于写入功能。

    #include <stdint.h>
    #include <string.h>
    
    #define LE (((char*)&(uint_least32_t){1})[0]) // little endian ? 
    void byteswap(char*,size_t);
    
    uint32_t s2_read_uint32(uint8_t** p)
    {
        uint32_t value;
        memcpy(&value,*p,sizeof(value));
        if(!LE) byteswap(&value,4);
        return *p+=4, value;
    }
    
     void s2_write_uint32(uint8_t** p, uint32_t value)
    {
        memcpy(*p,&value,sizeof(value));
        if(!LE) byteswap(*p,4);
        *p+=4;
    }
    

    自第 8 系列以来的 Gcc(但不是 clang)可以在 little-endian 平台上消除这种转变,但您应该通过restrict-限定指向目的地的双重间接指针来帮助它,否则它可能会认为对(*p)[0] 的写入会使*p 无效(uint8_t 是一种字符类型,因此允许为任何内容设置别名)。

    void s_write_uint32(uint8_t** restrict p, uint32_t value)
    {
        (*p)[0] = value & 0xFF;
        (*p)[1] = (value >> 8 ) & 0xFF;
        (*p)[2] = (value >> 16) & 0xFF;
        (*p)[3] = value >> 24;
        *p += 4;
    }
    

    【讨论】:

      猜你喜欢
      • 2010-10-30
      • 1970-01-01
      • 1970-01-01
      • 2012-07-15
      • 2014-10-02
      • 1970-01-01
      • 1970-01-01
      • 2014-10-27
      • 2016-09-24
      相关资源
      最近更新 更多