【问题标题】:What is the endianness of binary literals in C++14?C ++ 14中二进制文字的字节顺序是什么?
【发布时间】:2015-02-17 12:16:00
【问题描述】:

我尝试过四处搜索,但找不到太多关于二进制文字和字节序的信息。二进制字面量是 little-endian、big-endian 还是其他(例如匹配目标平台)?

例如,0b0111 的十进制值是多少?是7吗?特定平台?还有什么? 编辑:我选择了一个错误的值 7,因为它在一个字节内表示。尽管有这个事实,这个问题已经得到了充分的回答。

一些背景:基本上我试图找出最低有效位的值是什么,并且用二进制文字掩盖它似乎是一个好方法......但前提是有一些关于字节序的保证。

【问题讨论】:

  • 二进制字面量的工作方式与十进制字面量完全相同,只是它们是用二进制而不是十进制编写的。它们没有字节序。
  • 我真的很好奇:什么是反对票和接近票?我活跃于 SO,但不是 C++ 社区。这个问题有什么不好?它似乎不是重复的,这是一个直接的技术问题。请给我一些进一步的指导吗?
  • @LeviMorrison 你要的是不存在的东西。 c++ 或 c++11 没有字节序的概念,它是一种机器架构属性。
  • 这个问题没有什么特别的问题。似乎更多的是混淆了字节顺序的含义(或者可能是数字文字),但我不明白这是一个问题。
  • @Cubic:十进制文字当然有字节序。这就是为什么 7x3 是 21 而不是 12 的原因。任何有序的数字序列,无论基数如何,都具有字节顺序。由于顺序可以升序或降序,自然有大端和小端。 (“中端”是那些奇怪的3412 无序序列)

标签: c++ endianness c++14


【解决方案1】:

简答:没有。按照你在纸上写的方式写数字。

长答案: 字节序永远不会直接暴露在代码中,除非您真的尝试将其取出(例如使用指针技巧)。 0b0111是7,和hex规则一样,写法

int i = 0xAA77;

在某些平台上并不意味着0x77AA,因为那太荒谬了。对于 32 位整数,缺少的额外 0 会去哪里?他们会在前面被填充,然后整个事情翻转到0x77AA0000,还是会在之后被添加?如果是这样的话,我不知道有人会期待什么。

关键是 C++ 不对机器的字节顺序做任何假设*,如果您使用原语及其提供的文字编写代码,则机器之间的行为将是相同的(除非您开始规避类型系统,您可能需要这样做)。

为了解决您的更新:数字将是您写出来的方式。这些位不会被重新排序或任何类似的事情,最高有效位在左边,最低有效位在右边。


这里似乎对什么是字节序存在误解。字节序是指字节在内存中的排序方式以及它们必须如何解释。如果我给你数字“4172”并说“如果这是四千一百七十二,那么字节序是多少”你不能真正给出答案,因为这个问题没有意义。 (有些人认为左边的最大数字意味着大端,但如果没有记忆地址,端的问题就无法回答或相关)。这只是一个数字,没有要解释的字节,没有内存地址。假设4字节整数表示,对应的字节为:

        low address ----> high address
Big endian:    00 00 10 4c
Little endian: 4c 10 00 00

因此,鉴于其中任何一个并告诉“这是 4172 的计算机内部表示”,您可以确定它是小端还是大端。

所以现在考虑你的二进制文字 0b0111 这 4 位代表一个 nybble,并且可以存储为任一

              low ---> high
Big endian:    00 00 00 07
Little endian: 07 00 00 00

但您不必关心,因为这也是由硬件处理的,语言规定编译器从左到右读取,最高有效位到最低有效位

字节顺序与单个位无关。鉴于一个字节是 8 位,如果我递给你 0b00000111 并说“这是小端还是大端?”再说一次,你不能说,因为你只有一个字节(而且没有地址)。字节序与字节中的位顺序无关,它指的是整个字节相对于地址的顺序(当然,除非你有一位字节)。

您不必关心您的计算机在内部使用什么。 0b0111 只是节省了您编写诸如

之类的东西的时间
unsigned int mask = 7; // only keep the lowest 3 bits

通过写作

unsigned int mask = 0b0111;

无需评论解释数字的意义。


* 在 c++20 中,您可以使用 std::endian 检查字节顺序。

【讨论】:

  • @Jongware 好吧,您可以使用union 技巧来找出字节序。
  • @πάνταῥεῖ 做union 检查会违反工会规则,你可以这样做:int i = 1; char *cp = (char*)i; 然后*cp == 1 如果是小端则为真
  • @Medinoc 人们通常应该编写与字节序无关的代码。
  • 我想指出,在足够低的编程水平下,您无法避免字节序,因为您正在实施的任何规范都要求它们的输入或输出采用小/大/任何字节序。这包括网络协议、加密算法等。仅仅因为您不做这些事情并不意味着它们不存在,并且在这些情况下,字节序确实会从漂亮舒适的类型系统中泄漏出来。所以“太聪明了为自己好”的部分似乎没有根据。
  • @RyanHaining 使用您评论中的htons:这很容易实现,无需对字节顺序做出任何假设:uint16_t htons(uint16_t x) { uint16_t result; unsigned char *p = (unsigned char *) &result; p[0] = x >> 8; p[1] = x; return result; } 它确实对uint16_t 的表示做出了一些假设,但字节顺序是不是这些假设之一,至少 clang 很好地优化了这一点。我同意人们通常应该编写不对字节序进行假设的代码的评论,这是没有必要的。
【解决方案2】:

字节顺序是实现定义的。该标准保证每个对象都有一个对象表示为charunsigned char 的数组,您可以通过调用memcpy()memcmp() 来处理它们。在 C++17 中,将 reinterpret_cast 指向任何对象类型的指针或引用(不是指向 void、指向函数的指针或 nullptr)指向指向 char、@987654329 的指针是合法的@ 或 std::byte,它们是任何对象类型的有效别名。

人们谈论“字节序”时的意思是该对象表示中的字节顺序。例如,如果您声明 unsigned char int_bytes[sizeof(int)] = {1};int i; 然后 memcpy( &i, int_bytes, sizeof(i)); 您会得到 0x01、0x01000000、0x0100、0x0100000000000000 还是其他?答案是:是的。现实世界的实现会产生这些结果中的每一个,并且它们都符合标准。这样做的原因是编译器可以使用 CPU 的原生格式。

当程序需要通过 Internet 发送或接收数据时最常出现这种情况,其中所有标准都定义数据应以大端顺序在 x86 等小端 CPU 上传输。因此,一些网络库会指定结构的特定参数和字段是否应按主机或网络字节顺序存储。

该语言让您可以通过任意旋转对象表示的位来打自己的脚,但它可能会给您一个陷阱表示,如果您稍后尝试使用它可能会导致未定义的行为. (这可能意味着,例如,重写虚函数表以注入任意代码。)<type_traits> 标头有几个模板来测试使用对象表示执行操作是否安全。如果该类型为is_trivially_copyable,您可以使用memcpy( &dest, &src, sizeof(dest) ) 将一个对象复制到另一个相同类型的对象上。如果is_trivially_move_constructible,您可以复制到正确对齐的未初始化内存。如果类型为has_unique_object_representations,您可以测试相同类型的两个对象是否与memcmp( &a, &b, sizeof(a) ) 相同,并通过对其对象表示中的字节应用散列函数来正确散列对象。整数类型没有陷阱表示,依此类推。但是,在大多数情况下,如果您对字节序很重要的对象表示进行操作,您就是在告诉编译器假设您知道自己在做什么,并且您的代码将不可移植。

正如其他人所提到的,二进制文字首先使用最高有效数字编写,例如十进制、八进制或十六进制文字。这与字节序不同,不会影响您是否需要在从 Internet 读取的 TCP 标头的端口号上调用 ntohs()

【讨论】:

    【解决方案3】:

    一张图片有时超过千字。

    【讨论】:

    • 最佳答案。 C++ 源代码中的文字是大端的,就像我们通常在数学中表示以 10 为底的数字一样。字节的内存顺序将根据您的硬件而有所不同。
    【解决方案4】:

    所有整数文字,包括二进制文字,都以与我们通常读取数字相同的方式解释(最左边的数字是最重要的)。

    C++ 标准保证文字的相同解释,而不必担心您所处的特定环境。因此,在这种情况下,您不必担心字节顺序。

    0b0111 的示例始终等于 7。

    C++ 标准在数字文字方面不使用字节顺序术语。相反,它只是描述文字具有一致的解释,并且解释是您所期望的。

    C++ 标准 - 整数文字 - 2.14.2 - 第 1 段

    整数字面量是一个数字序列,没有句点或 指数部分,可选的分隔单引号被忽略 在确定它的价值时。整数文字可能有一个前缀 指定其基数和指定其类型的后缀。 词法上的 数字序列的第一个数字是最重要的。一种 二进制整数文字(以 2 为基数)以 0b 或 0B 开头,包括 二进制数字序列。 八进制整数文字(以八为基数) 以数字 0 开头,由一系列八进制数字组成。 十进制整数文字(以十为基数)以 0 以外的数字开头 并由一系列十进制数字组成。十六进制整数 文字(十六进制)以 0x 或 0X 开头,由一个序列组成 十六进制数字,包括十进制数字和 十进制到十进制的字母 a 到 f 和 A​​ 到 F 十五。 [示例:数字 12 可以写为 12、014、0XC 或 0b1100。文字 1048576、1'048'576、0X100000、0x10'0000 和 0'004'000'000 都具有相同的值。 —结束示例]

    Wikipedia describes what endianness is, and uses our number system as an example to understand big-endian.

    术语 endian 和 endianness 指的是用于 在存储这些字节时解释组成数据字的字节 在计算机内存中。

    大端系统将单词的最高有效字节存储在 最小地址和最低有效字节存储在 最大地址(另见最高有效位)。小端序 相反,系统将最低有效字节存储在最小的 地址。

    字节顺序的一个例子是考虑十进制数 以位置值表示法写入和读取。假设一个书写系统 其中数字从左到右书写,最左边的位置是 类似于所用内存的最小地址,最右边 位置最大。例如,数字一百二十三 写成 1 2 3,百位在最左边。 任何阅读的人 这个数字也知道最左边的数字最大的地方 价值。这是每天遵循的大端约定的示例 生活。

    在这种情况下,我们将整数文字的数字视为“单词的字节”,而单词就是文字本身。此外,文字中最左边的字符被认为具有最小的地址。

    对于文字1234,数字一、二、三和四是“单词的字节”,1234 是“单词”。对于二进制文字0b0111,数字零、一、一和一是“字的字节”,字是0111

    这种考虑让我们能够在 C++ 语言的上下文中理解字节序,并表明整数文字类似于“大字节序”。

    【讨论】:

    • 大端是人类可读的顺序,因为大数字首先被编码。 Little endian 对小数字进行编码,首先有效地反转它们的顺序。
    • 大端 = 最高有效字节优先,小端 = 最低有效字节优先
    • 大端系统就是这种情况。
    • @cmaster 最小地址 = 左 = 第一个。当然,我们通常根本不将术语字节序用于数字字符串,而仅用于内存中的布局。因此,可以说“字节序”一词根本不适用于文字,或者它们始终是大字节序的。说文字总是小端绝对是错误的。
    • @cmaster 这对人类来说是不可读的。这只是惯例。也许“对于在当前更大的全球文明中长大的人来说是可读的”
    【解决方案5】:

    您可能想将 C 或 C++ 或任何其他语言视为本质上是小端(考虑按位运算符的工作原理)。如果底层硬件是大端,编译器会确保数据以大端存储(对于其他端也是如此),但是您的位操作就像数据是小端一样工作。要记住的是,就语言而言,数据是小端的。当您将数据从一种类型转换为另一种类型时,会出现与字节顺序相关的问题。只要你不这样做,你就很好。

    有人问我关于“C/C++ 语言本质上是小端”的说法,因此我提供了一个很多人都知道它是如何工作的例子,但我就开始吧。

    typedef union
    {
        struct {
            int a:1;
            int reserved:31;
        } bits;
    
        unsigned int value;
    } u;
    
    u test;
    test.bits.a = 1;
    test.bits.reserved = 0;
    
    printf("After bits assignment, test.value = 0x%08X\n", test.value);
    
    test.value = 0x00000001;
    
    printf("After value assignment, test.value = 0x%08X\n", test.value);
    

    小端系统上的输出:

    After bits assignment, test.value = 0x00000001
    After value assignment, test.value = 0x00000001
    

    大端系统上的输出:

    After bits assignment, test.value = 0x80000000
    After value assignment, test.value = 0x00000001
    

    那么,如果你不知道处理器的字节序,那么一切都在哪里呢?在小端系统中!因此,我说 C/C++ 语言本质上是小端。

    【讨论】:

    • 评论不用于扩展讨论;这个对话是moved to chat
    • 可以用汇编语言或任何其他有指针的语言编写类似的检查。所以这段代码只显示“little-endian 比 big-endian 更自然”;这不适用于 C/C++。此外,这与问题中的二进制文字完全无关。
    【解决方案6】:

    您忽略了源代码中编写的字节顺序和目标代码中表示的字节顺序之间的区别。每个问题的答案都不足为奇:源代码文字是 bigendian,因为这是人类阅读它们的方式,在目标代码中它们是由目标代码编写的。

    由于一个字节被定义为内存访问的最小单位,我认为甚至不可能将字节序分配给字节中位的任何内部表示——这是发现更大数字字节序的唯一方法(无论是有意还是无意)都是通过分段从存储中访问它们,而字节根据定义是最小的可访问存储单元。

    【讨论】:

    • 在算术运算符的意义上,抽象机器说整数类型中的位是大端的:右移一个数字会产生更小的东西。当然,这与位或字节在内存设备中的存储方式无关。
    • @Hurkyl 完全正确。您无法判断机器寄存器是否是 bigendian,因为它们从未暴露过 - 没有理由在寄存器中暴露任何字节序但 bigendianness,因为 littlendian 的全部意义在于与外部的苏打稻草 8 位数据总线兼容存储或设备。
    【解决方案7】:

    C/C++ 语言不关心多字节整数的字节顺序。 C/C++ 编译器可以。编译器解析您的源代码并为特定目标平台生成机器代码。一般来说,编译器存储整数文字的方式与存储整数的方式相同。这样目标CPU的指令将直接支持在内存中读写。

    编译器会处理目标平台之间的差异,因此您不必这样做。

    唯一需要担心字节序的情况是当您与其他具有不同字节顺序的系统共享二进制值时。然后您将逐字节读取二进制数据,并将内存中的字节排列在正确的位置为您的代码正在运行的系统订购。

    【讨论】:

    • 如果您通过char 指针操作数据,您还需要担心字节顺序。
    • 如果 char 指针指向一个 int,您可以将其转换为一个 int 指针并照此使用。
    • @TheronWGenaux:并非总是如此 - 可能无法保证 int 正确对齐。
    • @psmears:非常正确。我记得,我认为是 8086 处理器,不需要对齐。我正在帮助某人弄清楚为什么它运行得这么慢。我们发现堆栈被设置为一个奇数地址,它对堆栈上的每个 push/pop 执行 2 次读取/写入。
    • @TheronWGenaux:哈哈,调试起来一定很有趣!是的,x86 处理器默认模拟未对齐的读取,它可以工作(尽管速度很慢);另一个处理器上的相同代码将产生总线错误。当您在 x86 上进行编码和测试时,这很有趣,然后部署到不同的(例如嵌入式)CPU...
    猜你喜欢
    • 2021-07-25
    • 1970-01-01
    • 1970-01-01
    • 2013-04-26
    • 2016-10-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多