【问题标题】:c++ combining 2 uint8_t into one uint16_t not working?c++ 将 2 个 uint8_t 合并为一个 uint16_t 不起作用?
【发布时间】:2019-07-25 11:29:51
【问题描述】:

所以我有一小段代码需要 2 个 uint8_t,然后彼此相邻放置,然后返回一个 uint16_t。重点不是添加 2 个变量,而是将它们彼此相邻并从它们创建一个 uint16_t。 我希望它起作用的方式是,当第一个 uint8_t 为 0,第二个 uint8_t 为 1 时,我希望 uint16_t 也是一个。 但是,在我的代码中并非如此。 这是我的代码:

uint8_t *bytes = new uint8_t[2];
bytes[0] = 0;
bytes[1] = 1;
uint16_t out = *((uint16_t*)bytes);

应该是将字节的uint8_t指针变成uint16_t指针,然后取值。我希望该值为 1,因为 x86 是小端。但是它返回 256。 将第一个字节设置为 1,将第二个字节设置为 0 使其按预期工作。但我想知道为什么我需要切换字节才能使其工作。

谁能给我解释一下?

谢谢!

【问题讨论】:

  • 您将此问题标记为“字节序”。那你到底有什么不明白的?因为字节序基本上就是答案。
  • "Little endian" 表示0xABCD 排列为0xCD, 0xAB。您是否混淆了这两种字节序?
  • 是的,我把他们弄糊涂了。以为小端序它以一点点结束。
  • 是的,我讨厌这些名字。正是出于这个原因,我总是把它们混在我的脑海里。为什么“结束”意味着“第一个结束”是我无法理解的。试着记住大端是你在拉丁系统中写数字的方式(例如 12345)(即对于我们中的许多人来说,“正常”)然后从那里开始工作

标签: c++ endianness


【解决方案1】:

该地址没有uint16_t 或兼容对象,因此*((uint16_t*)bytes) 的行为未定义。

我希望该值为 1,因为 x86 是小端。但是它返回 256。

即使程序被固定为具有明确定义的行为,您的期望也会倒退。在 little endian 中,最低有效字节存储在最低地址中。因此 2 字节值 1 存储为 1, 0 而不是 0, 1。

字节序是否也会影响字节中位的顺序?

没有办法通过“地址”1访问位,所以没有字节序的概念。转换为文本时,通常会在左侧显示最高有效位,在右侧显示最低有效位;就像十进制数字一样。我不知道这在从右到左的书写系统中是否正确。

1 您可以使用位域为位创建“虚拟地址”。位域的顺序,即第一个位域是最重要还是最不重要是实现定义的,根本与字节字节序无关。


这是将两个八位字节设置为uint16_t 的正确方法。结果将取决于系统的字节顺序:

// no need to complicate a simple example with dynamic allocation
uint16_t out;
// note that there is an exception in language rules that
// allows accessing any object through narrow (unsigned) char
// or std::byte pointers; thus following is well defined
std::byte* data = reinterpret_cast<std::byte*>(&out);
data[0] = 1;
data[1] = 0;

请注意,假设输入采用本机字节顺序通常不是一个好的选择,尤其是在需要跨多个系统兼容时,例如通过网络通信或访问可能与其他系统共享的文件时。

在这些情况下,通信协议或文件格式通常会指定数据采用特定的字节序,该字节序可能与目标系统的本机字节序相同,也可能不同。网络通信中的事实标准是使用大端序。可以使用位移将数据特别是字节序转换为原生字节序,例如 Frodyne 的回答中所示。

【讨论】:

  • 确实,此处定义明确的行为需要std::copy(或等效项)。
  • 好吧,或者从uint16_t 开始,然后通过uint8_t* 写入其中。然后就打印这个混蛋
  • int 只是一组位。所以我不认为 C++ 可以区分,对吧?
  • "int 只是一组位。所以我认为 C++ 无法区分,对吧?" 这个陈述是错误的,这就是为什么你有UB,以及为什么它很重要。对象不仅仅是一堆比特。您不是在手动编程物理计算机。 C++ 是一个抽象,编译器/优化器非常、非常、非常复杂。他们会抓住一切可能的机会走捷径,并且会以一种在您违反合同时破坏的残酷方式进行。在这里,您假装 uint16_t 存在,而在学术上它不存在。这对编译器很重要。
  • 这是一个常见的误解,所以不要难过。许多编写 C++ 的人仍然采用 1970 年代的 C 方法进行编程(它只是内存中的一些字节,对吗?),但现实完全不同。请记住,您是在描述程序的行为(实际上并不是在对计算机的内存芯片进行编程),然后就可以了。
【解决方案2】:

在小端系统中,小字节放在最前面。换句话说:低字节放在偏移量 0 上,高字节放在偏移量 1 上(依此类推)。所以这个:

uint8_t* bytes = new uint8_t[2];
bytes[0] = 1;
bytes[1] = 0;
uint16_t out = *((uint16_t*)bytes);

产生您想要的out = 1 结果。

但是,正如您所看到的,这很容易出错,所以一般来说,我建议您不要尝试将内容正确放置在内存中然后将其转换,而是执行以下操作:

uint16_t out = lowByte + (highByte << 8);

无论字节顺序如何,这都适用于任何机器。

编辑:添加了位移解释。

x &lt;&lt; y 表示将x y 中的位向左移动(&gt;&gt; 将它们移动到右侧)。

如果 X 包含位模式 xxxxxxxx,而 Y 包含位模式 yyyyyyyy,则 (X &lt;&lt; 8) 生成模式:xxxxxxxx00000000Y + (X &lt;&lt; 8) 生成:xxxxxxxxyyyyyyyy

(而Y + (X&lt;&lt;8) + (Z&lt;&lt;16) 产生zzzzzzzzxxxxxxxxyyyyyyyy 等)

单次左移与乘以 2 相同,因此 X &lt;&lt; 8X * 2^8 = X * 256 相同。这意味着您也可以这样做:Y + (X*256) + (Z*65536),但我认为这些转变更清晰,更能表达意图。

再次注意:字节顺序无关紧要。左移 8 位总是会清除低 8 位。

您可以在此处阅读更多信息:https://en.wikipedia.org/wiki/Bitwise_operation。请注意算术移位和逻辑移位之间的区别 - 在 C/C++ 中,无符号值使用逻辑移位,而有符号值使用算术移位。

【讨论】:

  • @Frodyne 你能解释一下它是如何工作的吗?我还不知道位运算符。
  • @Its-a-me-mario 这里operator&lt;&lt;() 是向左的按位移位。假设您有两个字节,MSB == 0x05LSB == 0x08。然后你想将它们连接成一个uint16_t。将MSB 移动一个字节的大小(即8)会给你0x0500static_cast 的结果是uint16_t,以避免隐式转换警告)。然后将结果添加到LSB 并获得您想要的串联0x0508
  • @Its-a-me-mario 我刚刚在编辑中添加了一个简短的解释。但基本上是 Fareanor 所说的。
【解决方案3】:

如果p 是指向某个多字节值的指针,那么:

  • “Little-endian”表示p处的字节是最不重要的字节,换句话说,它包含值的0-7位。
  • “Big-endian”表示p 处的字节是最重要的字节,对于 16 位值,该字节为 8-15 位。

由于 Intel 是 little-endian,因此 bytes[0] 包含 uint16_t 值的位 0-7,而 bytes[1] 包含位 8-15。由于您尝试设置位 0,因此您需要:

bytes[0] = 1; // Bits 0-7
bytes[1] = 0; // Bits 8-15

【讨论】:

  • 谢谢!我混淆了字节序。我认为 little endian 意味着它以一点点结尾。(这将解释名称)
  • 字节序是否也会影响字节中位的顺序?
  • @Its-a-me-mario No.
【解决方案4】:

您的代码有效,但您误解了如何读取“字节”

#include <cstdint>
#include <cstddef>
#include <iostream>

int main()
{
    uint8_t *in = new uint8_t[2];
    in[0] = 3;
    in[1] = 1;
    uint16_t out = *((uint16_t*)in);

    std::cout << "out: " << out << "\n in: " << in[1]*256 + in[0]<< std::endl;

    return 0;
}

顺便说一句,你应该注意对齐,当你用这种方式投射时。

【讨论】:

    【解决方案5】:

    用数字思考的一种方法是使用 MSB 和 LSB 顺序
    即 MSB 是最高位,LSB 是最低位
    Little Endian 机器。

    例如

    (u)int32:  MSB:Bit 31 ...  LSB: Bit 0
    (u)int16:  MSB:Bit 15 ...  LSB: Bit 0
    (u)int8 :  MSB:Bit  7 ...  LSB: Bit 0
    
    with your cast to a 16Bit value the Bytes will arrange like this
    
    16Bit                <=  8Bit       8Bit
    MSB     ...    LSB       BYTE[1]    BYTE[0]
    Bit15          Bit0      Bit7 .. 0  Bit7 .. 0
    0000 0001 0000 0000      0000 0001  0000 0000
    
    which is 256 -> correct value.
    
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-01-11
      • 1970-01-01
      • 2014-07-23
      • 1970-01-01
      • 2021-09-22
      • 2013-01-17
      相关资源
      最近更新 更多