Boost C++ 库

...世界上最受推崇和专业设计的 C++ 库项目之一。 Herb SutterAndrei Alexandrescu, C++ 编码标准

概述

摘要

Boost.Endian 提供了工具来操作整数和用户定义类型的 endianness

  • 支持三种处理 endianness 的方法。每种方法都有长期成功使用的历史,并且每种方法都有优于其他方法的用例。

  • 主要用途

    • 数据可移植性。Endian 库支持通过外部介质或网络传输进行二进制数据交换,而无需考虑平台 endianness。

    • 程序可移植性。基于 POSIX 和基于 Windows 的操作系统传统上提供带有不可移植函数的库来执行 endian 转换。至少有四组不兼容的函数在常用。Endian 库在所有 C++ 平台上都是可移植的。

  • 次要用途:通过标准 C++ 整数类型不支持的大小和/或对齐方式来最小化数据大小。

endianness 简介

考虑以下代码

int16_t i = 0x0102;
FILE * file = fopen("test.bin", "wb"); // binary file!
fwrite(&i, sizeof(int16_t), 1, file);
fclose(file);

在装有 Intel CPU 的 OS X、Linux 或 Windows 系统上,“test.bin”输出文件的十六进制转储产生

0201

在装有 PowerPC CPU 的 OS X 系统或装有 SPARC CPU 的 Solaris 系统上,“test.bin”输出文件的十六进制转储产生

0102

这里发生的情况是,Intel CPU 以最低有效字节优先的顺序排列整数的字节,而 SPARC CPU 则将最高有效字节放在首位。一些 CPU,例如 PowerPC,允许操作系统选择应用哪种排序。

最高有效字节优先的排序传统上称为“大端”排序,而最低有效字节优先的排序传统上称为“小端”排序。这些名称来源于 Jonathan Swift 的讽刺小说《格列佛游记》,其中敌对王国从不同的末端打开他们的半熟鸡蛋。

请参阅 Wikipedia 的 Endianness 文章,以获得关于 endianness 的广泛讨论。

程序员通常可以忽略 endianness,除非在小端系统上读取核心转储。但是当在具有不同 endianness 的计算机系统之间交换二进制整数和二进制浮点值时,无论是通过物理文件传输还是通过网络,程序员都必须处理 endianness。并且当最小化内部或外部数据大小时,程序员可能也想使用该库。

Boost.Endian 库简介

Boost.Endian 提供了三种不同的处理 endianness 的方法。这三种方法都支持整数和用户定义类型 (UDT)。

每种方法都有长期成功使用的历史,并且每种方法都有优于其他方法的用例。请参阅 在转换函数、缓冲区类型和算术类型之间选择

Endian 转换函数

应用程序使用内置整数类型来保存值,并根据需要调用提供的转换函数来转换字节顺序。提供可变和不可变转换,并且每种转换都有无条件和条件变体。

Endian 缓冲区类型

应用程序使用提供的 endian 缓冲区类型来保存值,并显式地与内置整数类型之间进行转换。提供 8、16、24、32、40、48、56 和 64 位(即 1、2、3、4、5、6、7 和 8 字节)的缓冲区大小。为所有大小提供了未对齐的整数缓冲区类型,为 16、32 和 64 位大小提供了对齐的缓冲区类型。提供的特定类型是通用类模板的 typedef,可以直接用于不太常见的用例。

Endian 算术类型

应用程序使用提供的 endian 算术类型,它们提供与内置 C++ 算术类型相同的操作。所有转换都是隐式的。提供 8、16、24、32、40、48、56 和 64 位(即 1、2、3、4、5、6、7 和 8 字节)的算术大小。为所有大小提供了未对齐的整数类型,为 16、32 和 64 位大小提供了对齐的算术类型。提供的特定类型是通用类模板的 typedef,可以直接在通用代码中或用于不太常见的用例。

Boost Endian 是一个仅头文件的库,需要 C++11。

对内建函数的内置支持

大多数编译器,包括 GCC、Clang 和 Visual C++,都提供对字节交换内建函数的内置支持。Endian 库在可用时使用这些内建函数,因为它们可能会产生更小、更快的生成代码,尤其是在优化的构建中。

定义宏 BOOST_ENDIAN_NO_INTRINSICS 将禁止使用内建函数。当编译器没有内建函数支持或无法找到适当的头文件时,这很有用,可能是因为它是较旧的版本或支持库非常有限。

BOOST_ENDIAN_INTRINSIC_MSG 被定义为 "no byte swap intrinsics" 或描述正在使用的特定内建函数集的字符串。这对于消除缺失的内建函数作为性能问题的来源非常有用。

性能

考虑这个问题

示例 1

将 100 添加到文件中的大端值,然后将结果写入文件

Endian 算术类型方法 Endian 转换函数方法
big_int32_at x;

... read into x from a file ...


x += 100;


... write x to a file ...
int32_t x;

... read into x from a file ...

big_to_native_inplace(x);
x += 100;
native_to_big_inplace(x);

... write x to a file ...

在优化的构建中,无论机器的本机 endianness 如何,这两种方法之间都不会有性能差异。 这是因为优化编译器将为每种方法生成完全相同的代码。通过研究 GCC 和 Visual C++ 生成的汇编代码证实了这一结论。此外,执行 I/O 所花费的时间将决定此应用程序的速度。

现在考虑一个稍微不同的问题

示例 2

将一百万个值添加到文件中的大端值,然后将结果写入文件

Endian 算术类型方法 Endian 转换函数方法
big_int32_at x;

... read into x from a file ...



for (int32_t i = 0; i < 1000000; ++i)
  x += i;



... write x to a file ...
int32_t x;

... read into x from a file ...

big_to_native_inplace(x);

for (int32_t i = 0; i < 1000000; ++i)
  x += i;

native_to_big_inplace(x);

... write x to a file ...

使用 Endian 算术方法,在小端平台上,在循环内完成从大端到本机端,然后再返回大端的隐式转换。使用 Endian 转换函数方法,用户已确保转换在循环外完成,因此代码可能在小端平台上运行得更快。

计时

这些测试是在 Windows 7 下的 circa 2012 4 核小端 X64 Intel Core i5-3570K CPU @ 3.40GHz 的发布版本上运行的。

注意
Windows CPU 计时器具有非常高的粒度。重复运行相同的测试通常会产生明显不同的结果。

有关实际代码,请参阅 test/loop_time_test.cpp,有关构建设置,请参阅 benchmark/Jamfile.v2

Linux 虚拟机上的 GNU C++ 版本 4.8.2

迭代次数:10'000'000'000,内建函数:__builtin_bswap16 等。

测试用例 Endian 算术类型 Endian 转换函数

16 位对齐大端

8.46 秒

5.28 秒

16 位对齐小端

5.28 秒

5.22 秒

32 位对齐大端

8.40 秒

2.11 秒

32 位对齐小端

2.11 秒

2.10 秒

64 位对齐大端

14.02 秒

3.10 秒

64 位对齐小端

3.00 秒

3.03 秒

Microsoft Visual C++ 版本 14.0

迭代次数:10'000'000'000,内建函数:<cstdlib> _byteswap_ushort 等。

测试用例 Endian 算术类型 Endian 转换函数

16 位对齐大端

8.27 秒

5.26 秒

16 位对齐小端

5.29 秒

5.32 秒

32 位对齐大端

8.36 秒

5.24 秒

32 位对齐小端

5.24 秒

5.24 秒

64 位对齐大端

13.65 秒

3.34 秒

64 位对齐小端

3.35 秒

2.73 秒

总体常见问题解答

实现是否仅为头文件?

是。

是否支持 C++03 编译器?

否。自 1.84 版本起,需要 C++11。

实现是否使用编译器内建的字节交换内建函数?

是,如果可用。请参阅 内建函数的内置支持

为什么要关心 endianness?

二进制数据可移植性是主要用例。

除了可移植二进制文件或网络 I/O 格式之外,endianness 还有其他用途吗?

使用大小根据应用程序需求定制的未对齐整数类型是一种次要用途,可以节省内部或外部内存空间。例如,与 64 位类型之一相比,在大型数组中使用 big_int40_buf_tbig_int40_t 可以节省大量空间。

为什么要关心二进制 I/O?为什么不直接使用 C++ 标准库流插入器和提取器?
  • 数据交换格式通常指定二进制整数数据。二进制整数数据更小,因此 I/O 更快,文件大小更小。系统之间的传输成本更低。

  • 此外,二进制整数数据的大小是固定的,因此可以实现固定大小的磁盘记录而无需填充,从而简化排序并允许随机访问。

  • 缺点,例如无法在生成的文件上使用文本实用程序,限制了其在二进制 I/O 优势至上的应用程序中的用途。

哪个更好,大端还是小端?

大端往往在网络环境中更受欢迎,并且更像是一个行业标准,但小端可能更适合主要在 x86、x86-64 和其他小端 CPU 上运行的应用程序。《维基百科》文章给出了更多优点和缺点。

为什么只支持大端和小端本机 endianness?

这些是当今唯一具有任何实际价值的 endian 方案。PDP-11 和其他中间 endian 方法是有趣的奇物,但与当今的 C++ 开发人员无关。对于允许运行时 endianness 切换的架构也是如此。《本机排序规范》经过精心设计,以便在将来需要时支持此类排序。感谢 Howard Hinnant 提出此建议。

为什么缓冲区和算术类型都存在?

缓冲区类型中的转换是显式的。算术类型中的转换是隐式的。这种根本的区别是一个有意的设计特性,如果继承层次结构被折叠,将会丢失。最初的设计只提供了算术类型。缓冲区类型是在正式审查期间由那些希望完全控制转换发生时间的人员提出的。他们还认为,缓冲区类型不太可能被不熟悉在 endian 算术整数类型上执行大量整数运算的含义的维护程序员误用。

与始终只使用算术类型相比,使用缓冲区类型有什么好处?

确保不执行隐藏转换。这对于关心实现速度极限的用户来说至关重要。“始终只使用算术类型”对于其他用户来说是可以接受的。当需要确保速度极限时,算术类型可以用于与缓冲区类型相同的设计模式或习惯用法中,从而为两种类型生成相同的代码。

整数支持的局限性是什么?

测试仅在使用二进制补码算术的机器上执行。Endian 转换函数仅支持 8、16、32 和 64 位对齐整数。endian 类型仅支持 8、16、24、32、40、48、56 和 64 位未对齐整数,以及 8、16、32 和 64 位对齐整数。

是否有浮点支持?

曾尝试支持四字节 float 和八字节 double,仅限于 IEEE 754(也称为 ISO/IEC/IEEE 60559)浮点数,并进一步限制于浮点 endianness 与整数 endianness 没有差异的系统。即使存在这些限制,对浮点类型的支持也不可靠,并且已被删除。例如,简单地反转浮点数的 endianness 可能会导致 signaling-NAN。

此后,已为 endian_bufferendian_arithmetic 和就地反转 endianness 的转换函数恢复了对 floatdouble 的支持。由于上述问题,采用值传递和返回的转换函数仍然不支持浮点数;反转浮点数的字节不一定会产生另一个有效的浮点数。

修订历史

1.84.0 版本的更改

  • 不再支持 C++03;需要 C++11 编译器。(这包括 GCC 4.6 或更高版本,以及 MSVC 12.0 (VS 2013) 或更高版本。)

1.75.0 版本的更改

  • endian_arithmetic 不再继承自 endian_buffer

  • 当定义 BOOST_ENDIAN_NO_CTORS 时,未对齐的 endian_bufferendian_arithmetic 是 C++03 POD,以启用 __attribute__((packed)) 的使用。

1.74.0 版本的更改

  • endian_reverse 中启用了作用域枚举类型。

  • endian_reverse_inplace 中启用了 boolenumfloatdouble

  • 为数组添加了 endian_reverse_inplace 的重载。

1.72.0 版本的更改

  • 在 GCC 和 Clang 上使 endian_reverseconditional_reverse*_to_* 成为 constexpr

  • 添加了便捷的加载和存储函数。

  • 添加了浮点便捷 typedef。

  • 添加了 data() 的非常量重载;将其返回类型更改为 unsigned char*

  • 在可用时为 endian_reverse 添加了 __int128 支持。

  • 添加了便捷头文件 boost/endian.hpp

1.71.0 版本的更改

  • 阐明了值类型模板参数的要求。

  • endian_bufferendian_arithmetic 添加了对 floatdouble 的支持。

  • 添加了 endian_loadendian_store

  • 更新了 endian_reverse 以正确支持所有非 bool 整数类型。

  • 将已弃用的名称移动到已弃用的头文件 endian.hpp

在转换函数、缓冲区类型和算术类型之间选择

注意
为特定应用程序决定最佳 endianness 方法(转换函数、缓冲区类型或算术类型)涉及复杂的工程权衡。在不了解不同接口的情况下很难评估这些权衡,因此您可能需要在继续之前阅读 转换函数缓冲区类型算术类型 页面。

特定应用程序的最佳 endianness 方法取决于应用程序的需求与三种方法中每种方法的特性之间的相互作用。

建议: 如果您是 endianness 的新手、不确定,或者不想花时间研究工程权衡,请使用 endian 算术类型。它们安全、易于使用且易于维护。如果需要,在性能热点(如冗长的循环)周围局部使用 预测需求 设计模式。

背景

处理 endianness 通常意味着程序可移植性或数据可移植性要求,通常两者都有。这意味着处理 endianness 的实际程序通常很复杂,因此此处显示的示例实际上将编写为分布在多个翻译单元中的多个函数。它们将涉及无法更改的接口,因为它们由第三方或标准库提供。

特性

区分 endianness 的三种方法的特性是 endianness 不变量、转换显式性、算术运算、可用大小和对齐要求。

Endianness 不变量

Endian 转换函数 使用普通 C++ 算术类型(如 intunsigned short)的对象来保存值。这打破了 C++ 语言规则适用的隐式不变量。只有当对象的 endianness 当前设置为平台的本机 endianness 时,通常的语言规则才适用。这会使推理逻辑流程变得非常困难,并导致难以找到的错误。

例如

struct data_t  // big endian
{
  int32_t   v1;  // description ...
  int32_t   v2;  // description ...
  ... additional character data members (i.e. non-endian)
  int32_t   v3;  // description ...
};

data_t data;

read(data);
big_to_native_inplace(data.v1);
big_to_native_inplace(data.v2);

...

++v1;
third_party::func(data.v2);

...

native_to_big_inplace(data.v1);
native_to_big_inplace(data.v2);
write(data);

程序员没有费心将 data.v3 转换为本机 endianness,因为未使用该成员。后来的维护人员需要将 data.v3 传递给第三方函数,因此在代码深处的某个位置添加了 third_party::func(data.v3);。这会导致静默失败,因为类型为 int32_t 的对象按照 C++ 核心语言描述的方式保存值的通常不变量不适用。

Endian 缓冲区和算术类型 在内部将值保存为字符数组,其不变量是数组的 endianness 永远不会改变。这使得这些类型更易于使用,程序更易于维护。

这是相同的示例,使用 endian 算术类型

struct data_t
{
  big_int32_t   v1;  // description ...
  big_int32_t   v2;  // description ...
  ... additional character data members (i.e. non-endian)
  big_int32_t   v3;  // description ...
};

data_t data;

read(data);

...

++v1;
third_party::func(data.v2);

...

write(data);

后来的维护人员可以添加 third_party::func(data.v3),它就可以正常工作。

转换显式性

Endian 转换函数缓冲区类型 永远不会执行隐式转换。这使用户可以显式控制转换何时发生,并可能有助于避免不必要的转换。

Endian 算术类型 隐式执行转换。这使得这些类型非常易于使用,但可能导致不必要的转换。未能将转换提升到内部循环之外可能会带来性能损失。

算术运算

Endian 转换函数 不提供算术运算,但这并不是问题,因为这种方法使用普通的 C++ 算术类型来保存值。

Endian 缓冲区类型 不提供算术运算。尽管这种方法避免了不必要的转换,但可能会导致引入额外的变量并使维护程序员感到困惑。

Endian 算术类型 提供算术运算。如果涉及大量算术运算,它们非常易于使用。

大小

Endianness 转换函数 仅支持 1、2、4 和 8 字节整数。这对于许多应用程序来说已经足够了。

Endian 缓冲区和算术类型 支持 1、2、3、4、5、6、7 和 8 字节整数。对于内存使用或 I/O 速度是限制因素的应用程序,使用根据应用程序需求定制的大小可能很有用。

对齐

Endianness 转换函数 仅支持对齐的整数和浮点类型。这对于大多数应用程序来说已经足够了。

Endian 缓冲区和算术类型 同时支持对齐和未对齐的整数和浮点类型。未对齐类型很少需要,但当需要时它们通常非常有用,并且解决方法很痛苦。例如

像这样的不可移植代码

struct S {
  uint16_t a; // big endian
  uint32_t b; // big endian
} __attribute__ ((packed));

可以用像这样的可移植代码替换

struct S {
  big_uint16_ut a;
  big_uint32_ut b;
};

设计模式

应用程序通常将 endian 数据作为记录或数据包进行传输,其中包含多个 endian 数据元素。为简单起见,我们将它们称为记录。

如果所需的 endianness 与本机 endianness 不同,则必须执行转换。该转换应该何时发生?已经发展出三种设计模式。

仅在需要时转换(即惰性)

此模式将转换推迟到代码中实际使用数据元素的位置。

当实际使用的 endian 元素根据记录内容或其他情况而变化很大时,此模式是合适的

预测需求进行转换

此模式执行转换为本机 endianness 的转换以预测使用,例如在读取记录后立即进行。如果需要,在所有可能的需要过去之后(例如在写入记录之前),执行转换为输出 endianness 的转换。

此模式的一种实现是在读取函数中创建一个 endianness 转换为本机的代理记录,并且仅将该代理公开给实现的其余部分。如果需要写入函数,则处理从本机到所需输出 endianness 的转换。

当记录中的所有 endian 元素通常都被使用,而与记录内容或其他情况无关时,此模式是合适的。

仅在需要时转换,除非在本地预测需求

此模式通常会延迟转换,但对于特定的本地需求,它会进行预测性转换。虽然特别适合与 endian 缓冲区或算术类型结合使用,但它也适用于转换函数。

示例

struct data_t
{
  big_int32_t   v1;
  big_int32_t   v2;
  big_int32_t   v3;
};

data_t data;

read(data);

...
++v1;
...

int32_t v3_temp = data.v3;  // hoist conversion out of loop

for (int32_t i = 0; i < large-number; ++i)
{
  ... lengthy computation that accesses v3_temp ...
}
data.v3 = v3_temp;

write(data);

一般来说,上面的伪代码将转换留给 endian 算术类型 big_int32_t。但是为了避免循环内的转换,在进入循环之前创建了一个临时变量,然后在循环完成后用于设置 data.v3 的新值。

问题:编译器的优化器难道不会将转换提升到循环之外吗?

答案:VC++ 2015 Preview,可能还有其他编译器,即使对于玩具测试程序也不会这样做。虽然节省量很小(两条寄存器 bswap 指令),但如果循环重复足够多次,则成本可能很高。另一方面,程序可能受 I/O 时间支配,即使是冗长的循环也无关紧要。

用例示例

移植 endian 无感知代码库

现有的代码库在大端系统上运行。它目前不处理 endianness。需要修改代码库,使其可以在各种操作系统下的小端系统上运行。为了简化过渡并保护现有文件的价值,外部数据将继续保持为大端。

建议使用 endian 算术方法 来满足这些需求。处理二进制 I/O 布局的相对少量的头文件需要更改类型。例如,shortint16_t 将更改为 big_int16_t.cpp 文件不需要更改。

移植 endian 感知代码库

现有的代码库在小端 Linux 系统上运行。它已经通过 Linux 提供的函数 处理 endianness。由于业务合并,代码库必须快速修改以支持 Windows 以及可能的其他操作系统,同时仍然支持 Linux。代码库是可靠的,程序员都非常了解 endian 问题。

所有这些因素都支持 endian 转换方法,该方法仅机械地将对 htobe32 等的调用更改为 boost::endian::native_to_big 等,并将 <endian.h> 替换为 <boost/endian/conversion.hpp>

可靠性和算术速度

将要开发一个新的、复杂的、多线程应用程序,该应用程序必须在小端机器上运行,但要执行大端网络 I/O。开发人员认为 endian 变量的计算速度至关重要,但已经看到许多错误是由于无法推理 endian 转换状态而导致的。他们还担心,如果使用成熟的 endian 算术类型,未来的维护更改可能会无意中引入大量缓慢的转换。

endian 缓冲区 方法是为此用例量身定制的。

可靠性和易用性

将要开发一个新的、复杂的、多线程应用程序,该应用程序必须在小端机器上运行,但要执行大端网络 I/O。开发人员认为 endian 变量的计算速度 并不关键,但已经看到许多错误是由于无法推理 endian 转换状态而导致的。他们还担心开发期间和长期维护的易用性。

消除对转换速度的担忧并增加对易用性的担忧,这强烈倾向于 endian 算术方法

Endian 转换函数

简介

头文件 boost/endian/conversion.hpp 提供了字节顺序反转和转换函数,这些函数在本机、大端或小端字节顺序之间转换内置整数类型的对象。也支持用户定义的类型。

参考

如果合适,函数以 inline 方式实现。

定义

Endianness 是指内部或外部整数和其他算术数据中字节的顺序。最高有效字节优先称为 大端 排序。最低有效字节优先称为 小端 排序。其他排序是可能的,并且一些 CPU 架构同时支持大端和小端排序。

注意
这些名称来源于 Jonathan Swift 的讽刺小说《格列佛游记》,其中敌对王国从不同的末端打开他们的半熟鸡蛋。维基百科对 Endianness 进行了广泛的描述。

标准整数类型(C++std [basic.fundamental])除了 bool 和作用域枚举类型(C++std [dcl.enum])统称为 endian 类型。在没有填充位的情况下,这在 Boost.Endian 库支持的平台上是正确的,endian 类型具有所有位模式都是有效值的属性,这意味着当 endian 类型的对象反转其组成字节时,结果是另一个有效值。这允许 endian_reverse 按值获取和返回。

其他内置类型,例如 boolfloat 或非作用域枚举,不具有相同的属性,这意味着反转其组成字节可能会产生无效值,从而导致未定义的行为。因此,这些类型在 endian_reverse 中是不允许的,但在 endian_reverse_inplace 中仍然允许。即使对象由于反转其字节而变得无效,只要从不读取其值,就不会出现未定义的行为。

头文件 <boost/endian/conversion.hpp> 概要

#define BOOST_ENDIAN_INTRINSIC_MSG \
   "message describing presence or absence of intrinsics"

namespace boost
{
namespace endian
{
  enum class order
  {
    native = /* see below */,
    big    = /* see below */,
    little = /* see below */,
  };

  // Byte reversal functions

  template <class Endian>
    Endian endian_reverse(Endian x) noexcept;

  template <class EndianReversible>
    EndianReversible big_to_native(EndianReversible x) noexcept;
  template <class EndianReversible>
    EndianReversible native_to_big(EndianReversible x) noexcept;
  template <class EndianReversible>
    EndianReversible little_to_native(EndianReversible x) noexcept;
  template <class EndianReversible>
    EndianReversible native_to_little(EndianReversible x) noexcept;

  template <order O1, order O2, class EndianReversible>
    EndianReversible conditional_reverse(EndianReversible x) noexcept;
  template <class EndianReversible>
    EndianReversible conditional_reverse(EndianReversible x,
      order order1, order order2) noexcept;

  // In-place byte reversal functions

  template <class EndianReversible>
    void endian_reverse_inplace(EndianReversible& x) noexcept;

  template<class EndianReversibleInplace, std::size_t N>
    void endian_reverse_inplace(EndianReversibleInplace (&x)[N]) noexcept;

  template <class EndianReversibleInplace>
    void big_to_native_inplace(EndianReversibleInplace& x) noexcept;
  template <class EndianReversibleInplace>
    void native_to_big_inplace(EndianReversibleInplace& x) noexcept;
  template <class EndianReversibleInplace>
    void little_to_native_inplace(EndianReversibleInplace& x) noexcept;
  template <class EndianReversibleInplace>
    void native_to_little_inplace(EndianReversibleInplace& x) noexcept;

  template <order O1, order O2, class EndianReversibleInplace>
    void conditional_reverse_inplace(EndianReversibleInplace& x) noexcept;
  template <class EndianReversibleInplace>
   void conditional_reverse_inplace(EndianReversibleInplace& x,
     order order1, order order2) noexcept;

  // Generic load and store functions

  template<class T, std::size_t N, order Order>
    T endian_load( unsigned char const * p ) noexcept;

  template<class T, std::size_t N, order Order>
    void endian_store( unsigned char * p, T const & v ) noexcept;

  // Convenience load functions

  boost::int16_t load_little_s16( unsigned char const * p ) noexcept;
  boost::uint16_t load_little_u16( unsigned char const * p ) noexcept;
  boost::int16_t load_big_s16( unsigned char const * p ) noexcept;
  boost::uint16_t load_big_u16( unsigned char const * p ) noexcept;

  boost::int32_t load_little_s24( unsigned char const * p ) noexcept;
  boost::uint32_t load_little_u24( unsigned char const * p ) noexcept;
  boost::int32_t load_big_s24( unsigned char const * p ) noexcept;
  boost::uint32_t load_big_u24( unsigned char const * p ) noexcept;

  boost::int32_t load_little_s32( unsigned char const * p ) noexcept;
  boost::uint32_t load_little_u32( unsigned char const * p ) noexcept;
  boost::int32_t load_big_s32( unsigned char const * p ) noexcept;
  boost::uint32_t load_big_u32( unsigned char const * p ) noexcept;

  boost::int64_t load_little_s40( unsigned char const * p ) noexcept;
  boost::uint64_t load_little_u40( unsigned char const * p ) noexcept;
  boost::int64_t load_big_s40( unsigned char const * p ) noexcept;
  boost::uint64_t load_big_u40( unsigned char const * p ) noexcept;

  boost::int64_t load_little_s48( unsigned char const * p ) noexcept;
  boost::uint64_t load_little_u48( unsigned char const * p ) noexcept;
  boost::int64_t load_big_s48( unsigned char const * p ) noexcept;
  boost::uint64_t load_big_u48( unsigned char const * p ) noexcept;

  boost::int64_t load_little_s56( unsigned char const * p ) noexcept;
  boost::uint64_t load_little_u56( unsigned char const * p ) noexcept;
  boost::int64_t load_big_s56( unsigned char const * p ) noexcept;
  boost::uint64_t load_big_u56( unsigned char const * p ) noexcept;

  boost::int64_t load_little_s64( unsigned char const * p ) noexcept;
  boost::uint64_t load_little_u64( unsigned char const * p ) noexcept;
  boost::int64_t load_big_s64( unsigned char const * p ) noexcept;
  boost::uint64_t load_big_u64( unsigned char const * p ) noexcept;

  // Convenience store functions

  void store_little_s16( unsigned char * p, boost::int16_t v ) noexcept;
  void store_little_u16( unsigned char * p, boost::uint16_t v ) noexcept;
  void store_big_s16( unsigned char * p, boost::int16_t v ) noexcept;
  void store_big_u16( unsigned char * p, boost::uint16_t v ) noexcept;

  void store_little_s24( unsigned char * p, boost::int32_t v ) noexcept;
  void store_little_u24( unsigned char * p, boost::uint32_t v ) noexcept;
  void store_big_s24( unsigned char * p, boost::int32_t v ) noexcept;
  void store_big_u24( unsigned char * p, boost::uint32_t v ) noexcept;

  void store_little_s32( unsigned char * p, boost::int32_t v ) noexcept;
  void store_little_u32( unsigned char * p, boost::uint32_t v ) noexcept;
  void store_big_s32( unsigned char * p, boost::int32_t v ) noexcept;
  void store_big_u32( unsigned char * p, boost::uint32_t v ) noexcept;

  void store_little_s40( unsigned char * p, boost::int64_t v ) noexcept;
  void store_little_u40( unsigned char * p, boost::uint64_t v ) noexcept;
  void store_big_s40( unsigned char * p, boost::int64_t v ) noexcept;
  void store_big_u40( unsigned char * p, boost::uint64_t v ) noexcept;

  void store_little_s48( unsigned char * p, boost::int64_t v ) noexcept;
  void store_little_u48( unsigned char * p, boost::uint64_t v ) noexcept;
  void store_big_s48( unsigned char * p, boost::int64_t v ) noexcept;
  void store_big_u48( unsigned char * p, boost::uint64_t v ) noexcept;

  void store_little_s56( unsigned char * p, boost::int64_t v ) noexcept;
  void store_little_u56( unsigned char * p, boost::uint64_t v ) noexcept;
  void store_big_s56( unsigned char * p, boost::int64_t v ) noexcept;
  void store_big_u56( unsigned char * p, boost::uint64_t v ) noexcept;

  void store_little_s64( unsigned char * p, boost::int64_t v ) noexcept;
  void store_little_u64( unsigned char * p, boost::uint64_t v ) noexcept;
  void store_big_s64( unsigned char * p, boost::int64_t v ) noexcept;
  void store_big_u64( unsigned char * p, boost::uint64_t v ) noexcept;

} // namespace endian
} // namespace boost

order::littleorder::big 的值不应彼此相等。

order::native 的值应为

  • 如果执行环境为大端,则等于 order::big,否则

  • 如果执行环境为小端,则等于 order::little,否则

  • 不等于 order::littleorder::big

要求

模板参数要求

boost/endian/conversion.hpp 头文件中的模板定义引用了各种命名要求,其详细信息在本小节的表中列出。在这些表中,T 是 C++ 程序实例化模板时提供的对象或引用类型;x 是类型(可能是 constT 的值;mlx 是类型 T 的可修改左值。

EndianReversible 要求(除了 CopyConstructible
表达式 返回 要求

endian_reverse(x)

T

T 是 endian 类型或类类型。

如果 T 是 endian 类型,则返回字节顺序反转的 x 值。

如果 T 是类类型,则该函数

  • 预计由用户实现,作为与 T 相同的命名空间中的非成员函数,可以通过参数依赖查找 (ADL) 找到;

  • 应返回 x 的值,其中类型或类型数组的所有数据成员的字节顺序都已反转,这些类型或类型数组满足 EndianReversible 要求。

EndianReversibleInplace 要求
表达式 要求

endian_reverse_inplace(mlx)

T 是整数类型、枚举类型、floatdouble、类类型或数组类型。

如果 T 不是类类型或数组类型,则反转 mlx 中的字节顺序。

如果 T 是类类型,则该函数

  • 预计由用户实现,作为与 T 相同的命名空间中的非成员函数,可以通过参数依赖查找 (ADL) 找到;

  • 应反转 mlx 的所有数据成员的字节顺序,这些数据成员具有满足 EndianReversibleEndianReversibleInplace 要求的类型或类型数组。

如果 T 是数组类型,则对每个元素调用 endian_reverse_inplace

注意
由于有一个用于类类型的 endian_reverse_inplace 函数模板,它调用 endian_reverse,因此用户定义的类型只需要 endian_reverse 即可满足 EndianReversibleInplace 要求。尽管不要求用户定义的类型提供 endian_reverse_inplace 函数,但这样做可能会提高效率。
用户定义类型 (UDT) 的自定义点

本小节描述了 Endian 库实现的要求。

需要 EndianReversible 的库函数模板需要通过对 endian_reverse() 进行非限定调用来执行所需的 endianness 反转。

需要 EndianReversibleInplace 的库函数模板需要通过对 endian_reverse_inplace() 进行非限定调用来执行所需的 endianness 反转。

有关用户定义类型的示例,请参阅 example/udt_conversion_example.cpp

字节反转函数

template <class Endian>
Endian endian_reverse(Endian x) noexcept;
  • 要求

    Endian 必须是标准整数类型(不是 bool)或作用域枚举类型。

    返回

    x,其组成字节的顺序已反转。

template <class EndianReversible>
EndianReversible big_to_native(EndianReversible x) noexcept;
  • 返回

    conditional_reverse<order::big, order::native>(x).

template <class EndianReversible>
EndianReversible native_to_big(EndianReversible x) noexcept;
  • 返回

    conditional_reverse<order::native, order::big>(x).

template <class EndianReversible>
EndianReversible little_to_native(EndianReversible x) noexcept;
  • 返回

    conditional_reverse<order::little, order::native>(x).

template <class EndianReversible>
EndianReversible native_to_little(EndianReversible x) noexcept;
  • 返回

    conditional_reverse<order::native, order::little>(x).

template <order O1, order O2, class EndianReversible>
EndianReversible conditional_reverse(EndianReversible x) noexcept;
  • 返回

    如果 O1 == O2, 则为 x,否则为 endian_reverse(x)

    备注

    应在编译时确定是要返回 x 还是 endian_reverse(x)

template <class EndianReversible>
EndianReversible conditional_reverse(EndianReversible x,
     order order1, order order2) noexcept;
  • 返回

    order1 == order2? x: endian_reverse(x).

就地字节反转函数

template <class EndianReversible>
void endian_reverse_inplace(EndianReversible& x) noexcept;
  • 效果

    EndianReversible 是类类型时,x = endian_reverse(x);。当 EndianReversible 是整数类型、枚举类型、floatdouble 时,反转 x 的组成字节的顺序。否则,程序格式不正确。

template<class EndianReversibleInplace, std::size_t N>
void endian_reverse_inplace(EndianReversibleInplace (&x)[N]) noexcept;
  • 效果

    i0N-1 调用 endian_reverse_inplace(x[i])

template <class EndianReversibleInplace>
void big_to_native_inplace(EndianReversibleInplace& x) noexcept;
  • 效果

    conditional_reverse_inplace<order::big, order::native>(x).

template <class EndianReversibleInplace>
void native_to_big_inplace(EndianReversibleInplace& x) noexcept;
  • 效果

    conditional_reverse_inplace<order::native, order::big>(x).

template <class EndianReversibleInplace>
void little_to_native_inplace(EndianReversibleInplace& x) noexcept;
  • 效果

    conditional_reverse_inplace<order::little, order::native>(x).

template <class EndianReversibleInplace>
void native_to_little_inplace(EndianReversibleInplace& x) noexcept;
  • 效果

    conditional_reverse_inplace<order::native, order::little>(x).

template <order O1, order O2, class EndianReversibleInplace>
void conditional_reverse_inplace(EndianReversibleInplace& x) noexcept;
  • 效果

    如果 O1 == O2, 则无,否则为 endian_reverse_inplace(x)

    备注

    应在编译时确定应用哪种效果。

template <class EndianReversibleInplace>
void conditional_reverse_inplace(EndianReversibleInplace& x,
     order order1, order order2) noexcept;
  • 效果

    如果 order1 == order2,则为 endian_reverse_inplace(x)

通用加载和存储函数

template<class T, std::size_t N, order Order>
T endian_load( unsigned char const * p ) noexcept;
  • 要求

    sizeof(T) 必须为 1、2、4 或 8。N 必须介于 1 和 sizeof(T) 之间(包括 1 和 sizeof(T))。T 必须是可平凡复制的。如果 N 不等于 sizeof(T),则 T 必须是整数或 enum

    效果

    p 开始读取 N 个字节,根据 Order 是否与本机 endianness 匹配,以正向或反向顺序读取,将生成的位模式解释为类型 T 的值,并返回它。如果 sizeof(T) 大于 N,则当 T 为无符号时进行零扩展,否则进行符号扩展。

template<class T, std::size_t N, order Order>
void endian_store( unsigned char * p, T const & v ) noexcept;
  • 要求

    sizeof(T) 必须为 1、2、4 或 8。N 必须介于 1 和 sizeof(T) 之间(包括 1 和 sizeof(T))。T 必须是可平凡复制的。如果 N 不等于 sizeof(T),则 T 必须是整数或 enum

    效果

    v 的对象表示形式中的 N 个最低有效字节写入 p,根据 Order 是否与本机 endianness 匹配,以正向或反向顺序写入。

便捷加载函数

inline boost::intM_t load_little_sN( unsigned char const * p ) noexcept;
  • p 读取 N 位有符号小端整数。

    返回

    endian_load<boost::intM_t, N/8, order::little>( p ).

inline boost::uintM_t load_little_uN( unsigned char const * p ) noexcept;
  • p 读取 N 位无符号小端整数。

    返回

    endian_load<boost::uintM_t, N/8, order::little>( p ).

inline boost::intM_t load_big_sN( unsigned char const * p ) noexcept;
  • p 读取 N 位有符号大端整数。

    返回

    endian_load<boost::intM_t, N/8, order::big>( p ).

inline boost::uintM_t load_big_uN( unsigned char const * p ) noexcept;
  • p 读取 N 位无符号大端整数。

    返回

    endian_load<boost::uintM_t, N/8, order::big>( p ).

便利函数

inline void store_little_sN( unsigned char * p, boost::intM_t v ) noexcept;
  • 将 N 位有符号小端整数写入 p

    效果

    endian_store<boost::intM_t, N/8, order::little>( p, v ).

inline void store_little_uN( unsigned char * p, boost::uintM_t v ) noexcept;
  • 将 N 位无符号小端整数写入 p

    效果

    endian_store<boost::uintM_t, N/8, order::little>( p, v ).

inline void store_big_sN( unsigned char * p, boost::intM_t v ) noexcept;
  • 将 N 位有符号大端整数写入 p

    效果

    endian_store<boost::intM_t, N/8, order::big>( p, v ).

inline void store_big_uN( unsigned char * p, boost::uintM_t v ) noexcept;
  • 将 N 位无符号大端整数写入 p

    效果

    endian_store<boost::uintM_t, N/8, order::big>( p, v ).

常见问题解答

有关库范围的常见问题解答,请参见 概述 FAQ

为什么同时提供返回值和就地修改函数?

通过值返回结果是标准的 C 和 C++ 惯用法,用于从参数计算值的函数。就地修改函数在许多实际的字节序用例中允许更简洁的代码,并且对于具有诸如不需要反转的字符串数据的成员的用户定义类型更有效。因此,同时提供了这两种形式。

为什么不使用 Linux 名称(htobe16、htole16、be16toh、le16toh 等)?

这些名称是非标准的,甚至在类 POSIX 操作系统之间也各不相同。C++ 库 TS 曾经打算使用这些名称,但发现它们有时被实现为宏。由于宏不遵守作用域和命名空间规则,因此使用它们将非常容易出错。

致谢

Tomas Puverle 在识别和阐明需要支持字节序转换(与字节序整数类型分开)方面发挥了重要作用。Phil Endecott 提出了返回值签名的形式。Vicente Botet 和其他审阅者建议支持用户定义的类型。Mathias Gaunard 建议使用 std::reverse 的通用反转模板实现方法。tymofey 建议 16 位、32 位和 64 位整数的可移植实现方法,Giovanni Piero Deretta 建议避免未定义行为,Pyry Jahkola 提出了进一步的改进。多位审阅者以及 David Stone 建议了 16 位、32 位和 64 位整数的内在 builtins 实现方法,David Stone 提供了他的 Boost 许可宏实现,该实现成为 boost/endian/detail/intrinsic.hpp 的起点。Pierre Talbot 提供了 int8_t endian_reverse() 和模板化的 endian_reverse_inplace() 实现。

Endian 缓冲区类型

简介

算术类型的内部字节顺序传统上称为字节序。有关字节序的完整探索,包括大端序小端序的定义,请参见 Wikipedia

头文件 boost/endian/buffers.hpp 提供了 endian_buffer,这是一个可移植的字节序整数二进制缓冲区类模板,可以控制字节顺序、值类型、大小和对齐方式,而与平台的本机字节序无关。typedefs 为常见配置提供了易于使用的名称。

用例主要涉及数据可移植性,无论是通过文件还是网络连接,但这些字节持有者也可用于减少内存使用、文件大小或网络活动,因为它们提供了其他方式无法获得的二进制数字大小。

endian_buffer 旨在让希望显式控制字节序转换发生时间的用户使用。它也充当 endian_arithmetic 类模板的基类,该模板旨在让希望完全自动的字节序转换并直接支持所有常规算术运算的用户使用。

示例

example/endian_example.cpp 程序写入一个二进制文件,其中包含四字节、大端序和小端序整数

#include <iostream>
#include <cstdio>
#include <boost/endian/buffers.hpp>  // see Synopsis below
#include <boost/static_assert.hpp>

using namespace boost::endian;

namespace
{
  //  This is an extract from a very widely used GIS file format.
  //  Why the designer decided to mix big and little endians in
  //  the same file is not known. But this is a real-world format
  //  and users wishing to write low level code manipulating these
  //  files have to deal with the mixed endianness.

  struct header
  {
    big_int32_buf_t     file_code;
    big_int32_buf_t     file_length;
    little_int32_buf_t  version;
    little_int32_buf_t  shape_type;
  };

  const char* filename = "test.dat";
}

int main(int, char* [])
{
  header h;

  BOOST_STATIC_ASSERT(sizeof(h) == 16U);  // reality check

  h.file_code   = 0x01020304;
  h.file_length = sizeof(header);
  h.version     = 1;
  h.shape_type  = 0x01020304;

  //  Low-level I/O such as POSIX read/write or <cstdio>
  //  fread/fwrite is sometimes used for binary file operations
  //  when ultimate efficiency is important. Such I/O is often
  //  performed in some C++ wrapper class, but to drive home the
  //  point that endian integers are often used in fairly
  //  low-level code that does bulk I/O operations, <cstdio>
  //  fopen/fwrite is used for I/O in this example.

  std::FILE* fi = std::fopen(filename, "wb");  // MUST BE BINARY

  if (!fi)
  {
    std::cout << "could not open " << filename << '\n';
    return 1;
  }

  if (std::fwrite(&h, sizeof(header), 1, fi) != 1)
  {
    std::cout << "write failure for " << filename << '\n';
    return 1;
  }

  std::fclose(fi);

  std::cout << "created file " << filename << '\n';

  return 0;
}

编译并执行 example/endian_example.cpp 后,test.dat 的十六进制转储显示

01020304 00000010 01000000 04030201

请注意,即使编译和运行该程序的机器是小端序的,前两个 32 位整数是大端序的,而后两个是小端序的。

局限性

需要 <climits>CHAR_BIT == 8。如果 CHAR_BIT 是其他值,编译将导致 #error。之所以存在此限制,是因为设计、实现、测试和文档仅考虑了与 8 位字节相关的问题,并且没有提出其他大小的实际用例。

功能集

  • 大端序| 小端序 | 本机字节序。

  • 有符号 | 无符号

  • 非对齐 | 对齐

  • 1-8 字节(非对齐)| 1、2、4、8 字节(对齐)

  • 值类型的选择

枚举和 typedef

提供了两个作用域枚举

enum class order { big, little, native };

enum class align { no, yes };

提供了一个类模板

template <order Order, typename T, std::size_t Nbits,
  align Align = align::no>
class endian_buffer;

typedefs,例如 big_int32_buf_t,为常见用例提供了方便的命名约定

名称 对齐方式 字节序 符号 大小(位)(n)

big_intN_buf_t

大端

有符号

8,16,24,32,40,48,56,64

big_uintN_buf_t

大端

无符号

8,16,24,32,40,48,56,64

little_intN_buf_t

小端

有符号

8,16,24,32,40,48,56,64

little_uintN_buf_t

小端

无符号

8,16,24,32,40,48,56,64

native_intN_buf_t

本机

有符号

8,16,24,32,40,48,56,64

native_uintN_buf_t

本机

无符号

8,16,24,32,40,48,56,64

big_intN_buf_at

大端

有符号

8,16,32,64

big_uintN_buf_at

大端

无符号

8,16,32,64

little_intN_buf_at

小端

有符号

8,16,32,64

little_uintN_buf_at

小端

无符号

8,16,32,64

非对齐类型不会导致编译器在类和结构中插入填充字节。这是一个重要的特性,可以利用它来最大限度地减少内存、文件和网络传输中浪费的空间。

注意
使用对齐类型的代码可能不具有可移植性,因为对齐要求在硬件架构之间有所不同,并且对齐方式可能会受到编译器开关或编译指示的影响。例如,64 位整数的对齐方式在 32 位机器上可能是 32 位边界,而在 64 位机器上可能是 64 位边界。此外,对齐类型仅在具有 8 位、16 位、32 位和 64 位整数类型的架构上可用。
提示
首选非对齐缓冲区类型。
提示
保护自己免受对齐问题的困扰。例如
static_assert(sizeof(containing_struct) == 12, "sizeof(containing_struct) is wrong");

注意:单字节大端和小端缓冲区类型在所有平台上都具有相同的布局,因此它们实际上永远不会反转字节序。提供它们是为了启用通用代码,并提高代码的可读性和可搜索性。

类模板 endian_buffer

endian_buffer 是算术类型的字节持有者,具有用户指定的字节序、值类型、大小和对齐方式。

概要

namespace boost
{
  namespace endian
  {
    enum class align { no, yes };

    template <order Order, class T, std::size_t Nbits,
      align Align = align::no>
    class endian_buffer
    {
    public:

      typedef T value_type;

      // if BOOST_ENDIAN_NO_CTORS is defined, these two
      // constructors will not be present

      endian_buffer() noexcept = default;
      explicit endian_buffer(T v) noexcept;

      endian_buffer& operator=(T v) noexcept;
      value_type value() const noexcept;
      unsigned char* data() noexcept;
      unsigned char const* data() const noexcept;

    private:

      unsigned char value_[Nbits / CHAR_BIT]; // exposition only
    };

    //  stream inserter
    template <class charT, class traits, order Order, class T,
      std::size_t n_bits, align Align>
    std::basic_ostream<charT, traits>&
      operator<<(std::basic_ostream<charT, traits>& os,
        const endian_buffer<Order, T, n_bits, Align>& x);

    //  stream extractor
    template <class charT, class traits, order Order, class T,
      std::size_t n_bits, align A>
    std::basic_istream<charT, traits>&
      operator>>(std::basic_istream<charT, traits>& is,
        endian_buffer<Order, T, n_bits, Align>& x);

    // typedefs

    // unaligned big endian signed integer buffers
    typedef endian_buffer<order::big, int_least8_t, 8>        big_int8_buf_t;
    typedef endian_buffer<order::big, int_least16_t, 16>      big_int16_buf_t;
    typedef endian_buffer<order::big, int_least32_t, 24>      big_int24_buf_t;
    typedef endian_buffer<order::big, int_least32_t, 32>      big_int32_buf_t;
    typedef endian_buffer<order::big, int_least64_t, 40>      big_int40_buf_t;
    typedef endian_buffer<order::big, int_least64_t, 48>      big_int48_buf_t;
    typedef endian_buffer<order::big, int_least64_t, 56>      big_int56_buf_t;
    typedef endian_buffer<order::big, int_least64_t, 64>      big_int64_buf_t;

    // unaligned big endian unsigned integer buffers
    typedef endian_buffer<order::big, uint_least8_t, 8>       big_uint8_buf_t;
    typedef endian_buffer<order::big, uint_least16_t, 16>     big_uint16_buf_t;
    typedef endian_buffer<order::big, uint_least32_t, 24>     big_uint24_buf_t;
    typedef endian_buffer<order::big, uint_least32_t, 32>     big_uint32_buf_t;
    typedef endian_buffer<order::big, uint_least64_t, 40>     big_uint40_buf_t;
    typedef endian_buffer<order::big, uint_least64_t, 48>     big_uint48_buf_t;
    typedef endian_buffer<order::big, uint_least64_t, 56>     big_uint56_buf_t;
    typedef endian_buffer<order::big, uint_least64_t, 64>     big_uint64_buf_t;

    // unaligned big endian floating point buffers
    typedef endian_buffer<order::big, float, 32>              big_float32_buf_t;
    typedef endian_buffer<order::big, double, 64>             big_float64_buf_t;

    // unaligned little endian signed integer buffers
    typedef endian_buffer<order::little, int_least8_t, 8>     little_int8_buf_t;
    typedef endian_buffer<order::little, int_least16_t, 16>   little_int16_buf_t;
    typedef endian_buffer<order::little, int_least32_t, 24>   little_int24_buf_t;
    typedef endian_buffer<order::little, int_least32_t, 32>   little_int32_buf_t;
    typedef endian_buffer<order::little, int_least64_t, 40>   little_int40_buf_t;
    typedef endian_buffer<order::little, int_least64_t, 48>   little_int48_buf_t;
    typedef endian_buffer<order::little, int_least64_t, 56>   little_int56_buf_t;
    typedef endian_buffer<order::little, int_least64_t, 64>   little_int64_buf_t;

    // unaligned little endian unsigned integer buffers
    typedef endian_buffer<order::little, uint_least8_t, 8>    little_uint8_buf_t;
    typedef endian_buffer<order::little, uint_least16_t, 16>  little_uint16_buf_t;
    typedef endian_buffer<order::little, uint_least32_t, 24>  little_uint24_buf_t;
    typedef endian_buffer<order::little, uint_least32_t, 32>  little_uint32_buf_t;
    typedef endian_buffer<order::little, uint_least64_t, 40>  little_uint40_buf_t;
    typedef endian_buffer<order::little, uint_least64_t, 48>  little_uint48_buf_t;
    typedef endian_buffer<order::little, uint_least64_t, 56>  little_uint56_buf_t;
    typedef endian_buffer<order::little, uint_least64_t, 64>  little_uint64_buf_t;

    // unaligned little endian floating point buffers
    typedef endian_buffer<order::little, float, 32>           little_float32_buf_t;
    typedef endian_buffer<order::little, double, 64>          little_float64_buf_t;

    // unaligned native endian signed integer types
    typedef endian_buffer<order::native, int_least8_t, 8>     native_int8_buf_t;
    typedef endian_buffer<order::native, int_least16_t, 16>   native_int16_buf_t;
    typedef endian_buffer<order::native, int_least32_t, 24>   native_int24_buf_t;
    typedef endian_buffer<order::native, int_least32_t, 32>   native_int32_buf_t;
    typedef endian_buffer<order::native, int_least64_t, 40>   native_int40_buf_t;
    typedef endian_buffer<order::native, int_least64_t, 48>   native_int48_buf_t;
    typedef endian_buffer<order::native, int_least64_t, 56>   native_int56_buf_t;
    typedef endian_buffer<order::native, int_least64_t, 64>   native_int64_buf_t;

    // unaligned native endian unsigned integer types
    typedef endian_buffer<order::native, uint_least8_t, 8>    native_uint8_buf_t;
    typedef endian_buffer<order::native, uint_least16_t, 16>  native_uint16_buf_t;
    typedef endian_buffer<order::native, uint_least32_t, 24>  native_uint24_buf_t;
    typedef endian_buffer<order::native, uint_least32_t, 32>  native_uint32_buf_t;
    typedef endian_buffer<order::native, uint_least64_t, 40>  native_uint40_buf_t;
    typedef endian_buffer<order::native, uint_least64_t, 48>  native_uint48_buf_t;
    typedef endian_buffer<order::native, uint_least64_t, 56>  native_uint56_buf_t;
    typedef endian_buffer<order::native, uint_least64_t, 64>  native_uint64_buf_t;

    // unaligned native endian floating point types
    typedef endian_buffer<order::native, float, 32>           native_float32_buf_t;
    typedef endian_buffer<order::native, double, 64>          native_float64_buf_t;

    // aligned big endian signed integer buffers
    typedef endian_buffer<order::big, int8_t, 8, align::yes>       big_int8_buf_at;
    typedef endian_buffer<order::big, int16_t, 16, align::yes>     big_int16_buf_at;
    typedef endian_buffer<order::big, int32_t, 32, align::yes>     big_int32_buf_at;
    typedef endian_buffer<order::big, int64_t, 64, align::yes>     big_int64_buf_at;

    // aligned big endian unsigned integer buffers
    typedef endian_buffer<order::big, uint8_t, 8, align::yes>      big_uint8_buf_at;
    typedef endian_buffer<order::big, uint16_t, 16, align::yes>    big_uint16_buf_at;
    typedef endian_buffer<order::big, uint32_t, 32, align::yes>    big_uint32_buf_at;
    typedef endian_buffer<order::big, uint64_t, 64, align::yes>    big_uint64_buf_at;

    // aligned big endian floating point buffers
    typedef endian_buffer<order::big, float, 32, align::yes>       big_float32_buf_at;
    typedef endian_buffer<order::big, double, 64, align::yes>      big_float64_buf_at;

    // aligned little endian signed integer buffers
    typedef endian_buffer<order::little, int8_t, 8, align::yes>    little_int8_buf_at;
    typedef endian_buffer<order::little, int16_t, 16, align::yes>  little_int16_buf_at;
    typedef endian_buffer<order::little, int32_t, 32, align::yes>  little_int32_buf_at;
    typedef endian_buffer<order::little, int64_t, 64, align::yes>  little_int64_buf_at;

    // aligned little endian unsigned integer buffers
    typedef endian_buffer<order::little, uint8_t, 8, align::yes>   little_uint8_buf_at;
    typedef endian_buffer<order::little, uint16_t, 16, align::yes> little_uint16_buf_at;
    typedef endian_buffer<order::little, uint32_t, 32, align::yes> little_uint32_buf_at;
    typedef endian_buffer<order::little, uint64_t, 64, align::yes> little_uint64_buf_at;

    // aligned little endian floating point buffers
    typedef endian_buffer<order::little, float, 32, align::yes>    little_float32_buf_at;
    typedef endian_buffer<order::little, double, 64, align::yes>   little_float64_buf_at;

    // aligned native endian typedefs are not provided because
    // <cstdint> types are superior for this use case

  } // namespace endian
} // namespace boost

公开的数据成员 value_endian_buffer 对象的当前值存储为字节序列,该序列的顺序由 Order 模板参数指定。CHAR_BIT 宏在 <climits> 中定义。唯一支持的 CHAR_BIT 值是 8。

Nbits 的有效值如下

  • sizeof(T) 为 1 时,Nbits 应为 8;

  • sizeof(T) 为 2 时,Nbits 应为 16;

  • sizeof(T) 为 4 时,Nbits 应为 24 或 32;

  • sizeof(T) 为 8 时,Nbits 应为 40、48、56 或 64。

不支持 sizeof(T) 的其他值。

Nbits 等于 sizeof(T)*8 时,T 必须是可平凡复制的类型(例如 float),并且假定其字节序与 uintNbits_t 相同。

Nbits 小于 sizeof(T)*8 时,T 必须是标准整型(C++std,[basic.fundamental])或 enum

成员

endian_buffer() noexcept = default;
  • 效果

    构造一个未初始化的对象。

explicit endian_buffer(T v) noexcept;
  • 效果

    endian_store<T, Nbits/8, Order>( value_, v ).

endian_buffer& operator=(T v) noexcept;
  • 效果

    endian_store<T, Nbits/8, Order>( value_, v ).

    返回

    *this.

value_type value() const noexcept;
  • 返回

    endian_load<T, Nbits/8, Order>( value_ ).

unsigned char* data() noexcept;
unsigned char const* data() const noexcept;
  • 返回

    指向 value_ 的第一个字节的指针。

非成员函数

template <class charT, class traits, order Order, class T,
  std::size_t n_bits, align Align>
std::basic_ostream<charT, traits>& operator<<(std::basic_ostream<charT, traits>& os,
  const endian_buffer<Order, T, n_bits, Align>& x);
  • 返回

    os << x.value().

template <class charT, class traits, order Order, class T,
  std::size_t n_bits, align A>
std::basic_istream<charT, traits>& operator>>(std::basic_istream<charT, traits>& is,
  endian_buffer<Order, T, n_bits, Align>& x);
  • 效果

    如同

    T i;
    if (is >> i)
      x = i;
    返回

    .

常见问题解答

有关库范围的常见问题解答,请参见 概述 FAQ

为什么不直接使用 Boost.Serialization?

序列化涉及对 I/O 中涉及的每个对象进行转换。字节序整数不需要转换或复制。它们已经处于二进制 I/O 的所需格式。因此,它们可以批量读取或写入。

字节序类型是 POD 吗?

对于 C++11 的 POD 定义,答案是“是”。对于 C++03 的 POD 定义,答案是“否”,尽管可以使用 BOOST_ENDIAN_NO_CTORS 来禁用构造函数并强制 C++03 POD 性(例如,GCC __attribute__((packed)) 扩展需要这样做。)

本机字节序有什么好处?

它提供了内置类型无法提供的对齐和大小保证。它简化了泛型编程。

为什么要使用对齐的字节序类型?

如果类型的字节序和对齐方式与机器的字节序和对齐方式要求匹配,则对齐的整数运算可能会更快(快 10 到 20 倍)。但是,与非对齐类型相比,代码的可移植性可能会稍差。

Boost.Endian 缓冲区的设计考虑

  • 必须适合 I/O - 换句话说,必须是可 memcpy 的。

  • 必须提供完全指定的大小和内部字节顺序。

  • 当内部整数表示的位数多于外部字节表示中的位数之和时,必须正确工作。当内部整数表示类型比外部字节中的位数之和多时,符号扩展必须正确工作。例如,使用 64 位整数在内部表示 40 位(5 字节)数字必须对正值和负值都有效。

  • 无论编译器将 char 视为有符号还是无符号,都必须正确工作(包括使用相同的已定义外部表示)。

  • 非对齐类型不得导致编译器插入填充字节。

  • 实现应非常谨慎地提供优化。经验表明,当更改机器或编译器时,字节序整数的优化通常会变成悲观化。当更改编译器开关、编译器版本或同一架构的 CPU 型号时,也可能发生悲观化。

编译

Boost.Endian 完全在标头中实现,无需链接到任何 Boost 对象库。

BOOST_ENDIAN_NO_CTORS 在定义时,会导致 class endian_buffer 没有构造函数。预期用途是保证 endian_buffer 是 C++03 POD。例如,GCC __attribute__((packed)) 扩展需要这样做。

Endian 算术类型

简介

头文件 boost/endian/arithmetic.hpp 提供了整数二进制类型,可以控制字节顺序、值类型、大小和对齐方式。typedefs 为常见配置提供了易于使用的名称。

这些类型为整数数据提供可移植的字节持有者,独立于特定的计算机体系结构。用例几乎总是涉及 I/O,无论是通过文件还是网络连接。尽管数据可移植性是主要动机,但这些整数字节持有者也可用于减少内存使用、文件大小或网络活动,因为它们提供了其他方式无法获得的二进制整数大小。

这种整数字节持有者类型传统上称为字节序类型。有关字节序的完整探索,包括大端序小端序的定义,请参见 Wikipedia

Boost 字节序整数提供与 C++ 标准整型相同的全套 C++ 赋值、算术和关系运算符,并具有标准语义。

一元算术运算符为 +-~!,以及前缀和后缀 --++。二元算术运算符为 +=-=**=//=&&=||=^^=<<<<=>>>>=。二元关系运算符为 ==!=<<=>>=

提供到基础值类型的隐式转换。提供了从基础值类型转换的隐式构造函数。

示例

endian_example.cpp 程序写入一个二进制文件,其中包含四字节、大端序和小端序整数

#include <iostream>
#include <cstdio>
#include <boost/endian/arithmetic.hpp>
#include <boost/static_assert.hpp>

using namespace boost::endian;

namespace
{
  //  This is an extract from a very widely used GIS file format.
  //  Why the designer decided to mix big and little endians in
  //  the same file is not known. But this is a real-world format
  //  and users wishing to write low level code manipulating these
  //  files have to deal with the mixed endianness.

  struct header
  {
    big_int32_t     file_code;
    big_int32_t     file_length;
    little_int32_t  version;
    little_int32_t  shape_type;
  };

  const char* filename = "test.dat";
}

int main(int, char* [])
{
  header h;

  BOOST_STATIC_ASSERT(sizeof(h) == 16U);  // reality check

  h.file_code   = 0x01020304;
  h.file_length = sizeof(header);
  h.version     = 1;
  h.shape_type  = 0x01020304;

  //  Low-level I/O such as POSIX read/write or <cstdio>
  //  fread/fwrite is sometimes used for binary file operations
  //  when ultimate efficiency is important. Such I/O is often
  //  performed in some C++ wrapper class, but to drive home the
  //  point that endian integers are often used in fairly
  //  low-level code that does bulk I/O operations, <cstdio>
  //  fopen/fwrite is used for I/O in this example.

  std::FILE* fi = std::fopen(filename, "wb");  // MUST BE BINARY

  if (!fi)
  {
    std::cout << "could not open " << filename << '\n';
    return 1;
  }

  if (std::fwrite(&h, sizeof(header), 1, fi) != 1)
  {
    std::cout << "write failure for " << filename << '\n';
    return 1;
  }

  std::fclose(fi);

  std::cout << "created file " << filename << '\n';

  return 0;
}

编译并执行 endian_example.cpp 后,test.dat 的十六进制转储显示

01020304 00000010 01000000 04030201

请注意,即使编译和运行该程序的机器是小端序的,前两个 32 位整数是大端序的,而后两个是小端序的。

局限性

需要 <climits>CHAR_BIT == 8。如果 CHAR_BIT 是其他值,编译将导致 #error。之所以存在此限制,是因为设计、实现、测试和文档仅考虑了与 8 位字节相关的问题,并且没有提出其他大小的实际用例。

功能集

  • 大端序| 小端序 | 本机字节序。

  • 有符号 | 无符号

  • 非对齐 | 对齐

  • 1-8 字节(非对齐)| 1、2、4、8 字节(对齐)

  • 值类型的选择

枚举和 typedef

提供了两个作用域枚举

enum class order { big, little, native };

enum class align { no, yes };

提供了一个类模板

template <order Order, typename T, std::size_t n_bits,
  align Align = align::no>
class endian_arithmetic;

typedefs,例如 big_int32_t,为常见用例提供了方便的命名约定

名称 对齐方式 字节序 符号 大小(位)(n)

big_intN_t

大端

有符号

8,16,24,32,40,48,56,64

big_uintN_t

大端

无符号

8,16,24,32,40,48,56,64

little_intN_t

小端

有符号

8,16,24,32,40,48,56,64

little_uintN_t

小端

无符号

8,16,24,32,40,48,56,64

native_intN_t

本机

有符号

8,16,24,32,40,48,56,64

native_uintN_t

本机

无符号

8,16,24,32,40,48,56,64

big_intN_at

大端

有符号

8,16,32,64

big_uintN_at

大端

无符号

8,16,32,64

little_intN_at

小端

有符号

8,16,32,64

little_uintN_at

小端

无符号

8,16,32,64

非对齐类型不会导致编译器在类和结构中插入填充字节。这是一个重要的特性,可以利用它来最大限度地减少内存、文件和网络传输中浪费的空间。

注意
使用对齐类型的代码可能不具有可移植性,因为对齐要求在硬件架构之间有所不同,并且对齐方式可能会受到编译器开关或编译指示的影响。例如,64 位整数的对齐方式在 32 位机器上可能是 32 位边界。此外,对齐类型仅在具有 8 位、16 位、32 位和 64 位整数类型的架构上可用。
提示
首选非对齐算术类型。
提示
保护自己免受对齐问题的困扰。例如
static_assert(sizeof(containing_struct) == 12, "sizeof(containing_struct) is wrong");
注意
单字节算术类型在所有平台上都具有相同的布局,因此它们实际上永远不会反转字节序。提供它们是为了启用通用代码,并提高代码的可读性和可搜索性。

类模板 endian_arithmetic

endian_arithmetic 是一个整数字节持有者,具有用户指定的字节序、值类型、大小和对齐方式。提供了算术类型的常用运算。

概要

#include <boost/endian/buffers.hpp>

namespace boost
{
  namespace endian
  {
    enum class align { no, yes };

    template <order Order, class T, std::size_t n_bits,
      align Align = align::no>
    class endian_arithmetic
    {
    public:

      typedef T value_type;

      // if BOOST_ENDIAN_NO_CTORS is defined, these two
      // constructors will not be present

      endian_arithmetic() noexcept = default;
      endian_arithmetic(T v) noexcept;

      endian_arithmetic& operator=(T v) noexcept;
      operator value_type() const noexcept;
      value_type value() const noexcept;
      unsigned char* data() noexcept;
      unsigned char const* data() const noexcept;

      // arithmetic operations
      //   note that additional operations are provided by the value_type
      value_type operator+() const noexcept;
      endian_arithmetic& operator+=(value_type y) noexcept;
      endian_arithmetic& operator-=(value_type y) noexcept;
      endian_arithmetic& operator*=(value_type y) noexcept;
      endian_arithmetic& operator/=(value_type y) noexcept;
      endian_arithmetic& operator%=(value_type y) noexcept;
      endian_arithmetic& operator&=(value_type y) noexcept;
      endian_arithmetic& operator|=(value_type y) noexcept;
      endian_arithmetic& operator^=(value_type y) noexcept;
      endian_arithmetic& operator<<=(value_type y) noexcept;
      endian_arithmetic& operator>>=(value_type y) noexcept;
      endian_arithmetic& operator++() noexcept;
      endian_arithmetic& operator--() noexcept;
      endian_arithmetic operator++(int) noexcept;
      endian_arithmetic operator--(int) noexcept;

      // Stream inserter
      template <class charT, class traits>
      friend std::basic_ostream<charT, traits>&
        operator<<(std::basic_ostream<charT, traits>& os, const endian_arithmetic& x);

      // Stream extractor
      template <class charT, class traits>
      friend std::basic_istream<charT, traits>&
        operator>>(std::basic_istream<charT, traits>& is, endian_arithmetic& x);
    };

    // typedefs

    // unaligned big endian signed integer types
    typedef endian_arithmetic<order::big, int_least8_t, 8>        big_int8_t;
    typedef endian_arithmetic<order::big, int_least16_t, 16>      big_int16_t;
    typedef endian_arithmetic<order::big, int_least32_t, 24>      big_int24_t;
    typedef endian_arithmetic<order::big, int_least32_t, 32>      big_int32_t;
    typedef endian_arithmetic<order::big, int_least64_t, 40>      big_int40_t;
    typedef endian_arithmetic<order::big, int_least64_t, 48>      big_int48_t;
    typedef endian_arithmetic<order::big, int_least64_t, 56>      big_int56_t;
    typedef endian_arithmetic<order::big, int_least64_t, 64>      big_int64_t;

    // unaligned big endian unsigned integer types
    typedef endian_arithmetic<order::big, uint_least8_t, 8>       big_uint8_t;
    typedef endian_arithmetic<order::big, uint_least16_t, 16>     big_uint16_t;
    typedef endian_arithmetic<order::big, uint_least32_t, 24>     big_uint24_t;
    typedef endian_arithmetic<order::big, uint_least32_t, 32>     big_uint32_t;
    typedef endian_arithmetic<order::big, uint_least64_t, 40>     big_uint40_t;
    typedef endian_arithmetic<order::big, uint_least64_t, 48>     big_uint48_t;
    typedef endian_arithmetic<order::big, uint_least64_t, 56>     big_uint56_t;
    typedef endian_arithmetic<order::big, uint_least64_t, 64>     big_uint64_t;

    // unaligned big endian floating point types
    typedef endian_arithmetic<order::big, float, 32>              big_float32_t;
    typedef endian_arithmetic<order::big, double, 64>             big_float64_t;

    // unaligned little endian signed integer types
    typedef endian_arithmetic<order::little, int_least8_t, 8>     little_int8_t;
    typedef endian_arithmetic<order::little, int_least16_t, 16>   little_int16_t;
    typedef endian_arithmetic<order::little, int_least32_t, 24>   little_int24_t;
    typedef endian_arithmetic<order::little, int_least32_t, 32>   little_int32_t;
    typedef endian_arithmetic<order::little, int_least64_t, 40>   little_int40_t;
    typedef endian_arithmetic<order::little, int_least64_t, 48>   little_int48_t;
    typedef endian_arithmetic<order::little, int_least64_t, 56>   little_int56_t;
    typedef endian_arithmetic<order::little, int_least64_t, 64>   little_int64_t;

    // unaligned little endian unsigned integer types
    typedef endian_arithmetic<order::little, uint_least8_t, 8>    little_uint8_t;
    typedef endian_arithmetic<order::little, uint_least16_t, 16>  little_uint16_t;
    typedef endian_arithmetic<order::little, uint_least32_t, 24>  little_uint24_t;
    typedef endian_arithmetic<order::little, uint_least32_t, 32>  little_uint32_t;
    typedef endian_arithmetic<order::little, uint_least64_t, 40>  little_uint40_t;
    typedef endian_arithmetic<order::little, uint_least64_t, 48>  little_uint48_t;
    typedef endian_arithmetic<order::little, uint_least64_t, 56>  little_uint56_t;
    typedef endian_arithmetic<order::little, uint_least64_t, 64>  little_uint64_t;

    // unaligned little endian floating point types
    typedef endian_arithmetic<order::little, float, 32>           little_float32_t;
    typedef endian_arithmetic<order::little, double, 64>          little_float64_t;

    // unaligned native endian signed integer types
    typedef endian_arithmetic<order::native, int_least8_t, 8>     native_int8_t;
    typedef endian_arithmetic<order::native, int_least16_t, 16>   native_int16_t;
    typedef endian_arithmetic<order::native, int_least32_t, 24>   native_int24_t;
    typedef endian_arithmetic<order::native, int_least32_t, 32>   native_int32_t;
    typedef endian_arithmetic<order::native, int_least64_t, 40>   native_int40_t;
    typedef endian_arithmetic<order::native, int_least64_t, 48>   native_int48_t;
    typedef endian_arithmetic<order::native, int_least64_t, 56>   native_int56_t;
    typedef endian_arithmetic<order::native, int_least64_t, 64>   native_int64_t;

    // unaligned native endian unsigned integer types
    typedef endian_arithmetic<order::native, uint_least8_t, 8>    native_uint8_t;
    typedef endian_arithmetic<order::native, uint_least16_t, 16>  native_uint16_t;
    typedef endian_arithmetic<order::native, uint_least32_t, 24>  native_uint24_t;
    typedef endian_arithmetic<order::native, uint_least32_t, 32>  native_uint32_t;
    typedef endian_arithmetic<order::native, uint_least64_t, 40>  native_uint40_t;
    typedef endian_arithmetic<order::native, uint_least64_t, 48>  native_uint48_t;
    typedef endian_arithmetic<order::native, uint_least64_t, 56>  native_uint56_t;
    typedef endian_arithmetic<order::native, uint_least64_t, 64>  native_uint64_t;

    // unaligned native endian floating point types
    typedef endian_arithmetic<order::native, float, 32>           native_float32_t;
    typedef endian_arithmetic<order::native, double, 64>          native_float64_t;

    // aligned big endian signed integer types
    typedef endian_arithmetic<order::big, int8_t, 8, align::yes>       big_int8_at;
    typedef endian_arithmetic<order::big, int16_t, 16, align::yes>     big_int16_at;
    typedef endian_arithmetic<order::big, int32_t, 32, align::yes>     big_int32_at;
    typedef endian_arithmetic<order::big, int64_t, 64, align::yes>     big_int64_at;

    // aligned big endian unsigned integer types
    typedef endian_arithmetic<order::big, uint8_t, 8, align::yes>      big_uint8_at;
    typedef endian_arithmetic<order::big, uint16_t, 16, align::yes>    big_uint16_at;
    typedef endian_arithmetic<order::big, uint32_t, 32, align::yes>    big_uint32_at;
    typedef endian_arithmetic<order::big, uint64_t, 64, align::yes>    big_uint64_at;

    // aligned big endian floating point types
    typedef endian_arithmetic<order::big, float, 32, align::yes>       big_float32_at;
    typedef endian_arithmetic<order::big, double, 64, align::yes>      big_float64_at;

    // aligned little endian signed integer types
    typedef endian_arithmetic<order::little, int8_t, 8, align::yes>    little_int8_at;
    typedef endian_arithmetic<order::little, int16_t, 16, align::yes>  little_int16_at;
    typedef endian_arithmetic<order::little, int32_t, 32, align::yes>  little_int32_at;
    typedef endian_arithmetic<order::little, int64_t, 64, align::yes>  little_int64_at;

    // aligned little endian unsigned integer types
    typedef endian_arithmetic<order::little, uint8_t, 8, align::yes>   little_uint8_at;
    typedef endian_arithmetic<order::little, uint16_t, 16, align::yes> little_uint16_at;
    typedef endian_arithmetic<order::little, uint32_t, 32, align::yes> little_uint32_at;
    typedef endian_arithmetic<order::little, uint64_t, 64, align::yes> little_uint64_at;

    // aligned little endian floating point types
    typedef endian_arithmetic<order::little, float, 32, align::yes>    little_float32_at;
    typedef endian_arithmetic<order::little, double, 64, align::yes>   little_float64_at;

    // aligned native endian typedefs are not provided because
    // <cstdint> types are superior for that use case

  } // namespace endian
} // namespace boost

唯一支持的 CHAR_BIT 值是 8。

Nbits 的有效值如下

  • sizeof(T) 为 1 时,Nbits 应为 8;

  • sizeof(T) 为 2 时,Nbits 应为 16;

  • sizeof(T) 为 4 时,Nbits 应为 24 或 32;

  • sizeof(T) 为 8 时,Nbits 应为 40、48、56 或 64。

不支持 sizeof(T) 的其他值。

Nbits 等于 sizeof(T)*8 时,T 必须是标准算术类型。

Nbits 小于 sizeof(T)*8 时,T 必须是标准整型(C++std,[basic.fundamental]),而不是 bool

成员

endian_arithmetic() noexcept = default;
  • 效果

    构造一个未初始化的对象。

endian_arithmetic(T v) noexcept;
  • 效果

    参见 endian_buffer::endian_buffer(T)

endian_arithmetic& operator=(T v) noexcept;
  • 效果

    参见 endian_buffer::operator=(T)

    返回

    *this.

value_type value() const noexcept;
  • 返回

    参见 endian_buffer::value()

unsigned char* data() noexcept;
unsigned char const* data() const noexcept;
  • 返回

    参见 endian_buffer::data()

operator T() const noexcept;
  • 返回

    value().

其他运算符

字节序对象上的其他运算符将转发到 value_type 上的等效运算符。

流插入器

template <class charT, class traits>
friend std::basic_ostream<charT, traits>&
  operator<<(std::basic_ostream<charT, traits>& os, const endian_arithmetic& x);
  • 返回

    os << +x.

流提取器

template <class charT, class traits>
friend std::basic_istream<charT, traits>&
  operator>>(std::basic_istream<charT, traits>& is, endian_arithmetic& x);
  • 效果

    如同

    T i;
    if (is >> i)
      x = i;
    返回

    .

常见问题解答

有关库范围的常见问题解答,请参见 概述 FAQ

为什么不直接使用 Boost.Serialization?

序列化涉及对 I/O 中涉及的每个对象进行转换。字节序整数不需要转换或复制。它们已经处于二进制 I/O 的所需格式。因此,它们可以批量读取或写入。

字节序类型是 POD 吗?

对于 C++11 的 POD 定义,答案是“是”。对于 C++03 的 POD 定义,答案是“否”,尽管可以使用 BOOST_ENDIAN_NO_CTORS 来禁用构造函数并强制 C++03 POD 性(例如,GCC __attribute__((packed)) 扩展需要这样做。)

本机字节序有什么好处?

它提供了内置类型无法提供的对齐和大小保证。它简化了泛型编程。

为什么要使用对齐的字节序类型?

如果类型的字节序和对齐方式与机器的字节序和对齐方式要求匹配,则对齐的整数运算可能会更快(快 10 到 20 倍)。但是,与非对齐类型相比,代码的可移植性会稍差。

为什么要提供算术运算?

提供全套运算可以减少程序混乱,并使代码更易于编写和阅读。考虑递增记录中的变量。写成

++record.foo;

而不是

int temp(record.foo);
++temp;
record.foo = temp;

Boost.Endian 类型的设计考虑

  • 必须适合 I/O - 换句话说,必须是可 memcpy 的。

  • 必须提供完全指定的大小和内部字节顺序。

  • 当内部整数表示的位数多于外部字节表示中的位数之和时,必须正确工作。当内部整数表示类型比外部字节中的位数之和多时,符号扩展必须正确工作。例如,使用 64 位整数在内部表示 40 位(5 字节)数字必须对正值和负值都有效。

  • 无论编译器将 char 视为有符号还是无符号,都必须正确工作(包括使用相同的已定义外部表示)。

  • 非对齐类型不得导致编译器插入填充字节。

  • 实现应非常谨慎地提供优化。经验表明,当更改机器或编译器时,字节序整数的优化通常会变成悲观化。当更改编译器开关、编译器版本或同一架构的 CPU 型号时,也可能发生悲观化。

经验

具有类似功能的类已由多位 Boost 程序员独立开发,并在高价值、高使用率的应用程序中成功使用了多年。这些独立开发的字节序库通常是从 C 库演变而来的,这些 C 库也被广泛使用。字节序类型已被证明在各种计算机体系结构和应用程序中非常有用。

动机用例

Neil Mayhew 写道:“我还可以为这个库提供一个有意义的用例:从磁盘读取 TrueType 字体文件并处理内容。数据格式具有固定的字节序(大端序),并且在各处都有非对齐的值。使用 Boost.Endian 极大地简化和清理了代码。”

编译

Boost.Endian 完全在标头中实现,无需链接到任何 Boost 对象库。

BOOST_ENDIAN_NO_CTORS 在定义时,会导致 class endian_buffer 没有构造函数。预期用途是保证 endian_buffer 是 C++03 POD。例如,GCC __attribute__((packed)) 扩展需要这样做。

致谢

原始设计由 Darin Adler 基于 Mark Borgerding 开发的类开发。Beman Dawes 将四个原始类模板组合成一个 endian_arithmetic 类模板,他将库放在一起,提供了文档,添加了 typedefs,并且还添加了 unrolled_byte_loops 符号部分特化,以便在覆盖整数大小与字节序表示大小不同时正确扩展符号。

附录 A:历史和致谢

历史

正式评审要求的更改

库从上到下进行了重新设计,以适应正式评审期间要求的更改。在迷你评审之前需要解决的问题在下面以粗体显示,并指示了解决方案。

应开发常见用例场景。

完成。文档已重构。现在有一页专门介绍 选择方法 来处理字节序。有关用例场景,请参见 用例

应为常见用例场景开发示例程序。

完成。请参见 选择方法。整个文档中都添加了示例代码。

文档应阐明字节序整数/浮点类型方法与字节序转换方法在常见用例场景中的差异,并为在用户应用程序中选择最合适的方法提供指南。

完成。请参见 选择方法

应提供通过返回值提供结果的转换函数。

完成。请参见 转换函数

应支持特定于平台的性能增强功能,例如使用编译器内在函数或放宽对齐要求。

完成。根据要求,实现中适当地使用了编译器(Clang、GCC、VisualC++ 等)内在函数和内置函数。请参见 对内在函数的内置支持。请参见 示例 2 的计时 以衡量内在函数的影响。

字节序整数(和浮点)类型应通过转换函数实现。如果无法高效地完成,则应考虑扩展转换函数签名以解决效率低下问题。

完成。对于字节序类型,实现根据要求使用了字节序转换函数,因此也使用了内在函数。

应提供衡量性能的基准。应该可以比较特定于平台的性能增强功能与可移植基础实现,以及比较常见用例场景的字节序整数方法与字节序转换方法。

完成。请参见 示例 2 的计时endian/test 目录还包含几个额外的基准测试和速度测试程序。

应支持浮点数(32 位)和双精度浮点数(64 位)。IEEE 754 是主要用例。

完成。现在,字节序缓冲区类型字节序算术类型字节序转换函数 支持 32 位 (float) 和 64 位 (double) 浮点数,如要求的那样。

注意
此答案已过时。随后发现对 floatdouble 的支持存在问题,并已删除。最近,已为 endian_bufferendian_arithmetic 重新启用对 floatdouble 的支持,但未为转换函数重新启用。
支持用户定义的类型 (UDT) 是可取的,并且应在不与其他关注点冲突的情况下提供。

完成。请参见 用户定义类型 (UDT) 的自定义点

有人担心字节序整数/浮点算术运算可能会被无意或不适当地使用。应调查添加不带算术运算的 endian_buffer 类的影响。

完成。字节序类型已分解为类模板 endian_buffer 和类模板 endian_arithmetic。类 endian_bufferendian_arithmetic 的公共基类,用户也可以将其用作独立的类。

字节序整数/浮点类型的流插入和提取应记录在文档中并包含在测试覆盖率中。

完成。请参见 流插入器流提取器

在 Endian 库开发期间调查的二进制 I/O 支持应提交迷你评审,以便包含在 Boost I/O 库中。

尚未完成。将在 Endian 迷你评审后不久作为单独的迷你评审处理。

其他要求的更改。
  • 除了命名字节序转换函数外,现在还提供了执行编译时(通过模板)和运行时(通过函数参数)分派的函数。

  • order::native 现在是 order::bigorder::little 的同义词,具体取决于平台的字节序。这减少了所需的模板特化的数量。

  • 标头已重新组织,使其更易于阅读,前面是概要,后面是实现。

自正式评审以来的其他更改

  • 标头 boost/endian/endian.hpp 已重命名为 boost/endian/arithmetic.hpp。已添加标头 boost/endian/conversion.hppboost/endian/buffers.hpp。基础结构文件名也相应地更改了。

  • 字节序算术类型别名已重命名,使用整数和浮点数一致的命名模式,并为字节序缓冲区类型提供了一致的别名集。

  • 非对齐类型别名名称仍然具有 _t 后缀,但对齐类型别名名称现在具有 _at 后缀。

  • 添加了 endian_reverse()int8_tuint8_t 的重载,以提高通用性。(Pierre Talbot)

  • endian_reverse_inplace() 的重载已替换为单个 endian_reverse_inplace() 模板。(Pierre Talbot)

  • 对于允许非对齐加载和存储的 X86 和 X64 架构,当大小完全匹配时,非对齐小端缓冲区和算术类型使用常规加载和存储。这使得非对齐小端缓冲区和算术类型在这些架构上效率更高。(Jeremy Maitin-Shepard)

  • 现在使用影响接口的 C++11 功能,例如 noexcept。仍然支持 C++03 编译器。

  • 致谢已更新。

与临时版本的兼容性

在 Boost 官方发布之前,类模板 endian_arithmetic 已使用十年或更长时间,功能相同,但名称为 endian。其他名称也在官方版本中更改。如果定义了宏 BOOST_ENDIAN_DEPRECATED_NAMES,则仍然支持那些旧的、现已弃用的名称。但是,类模板 endian 名称仅为支持 C++11 模板别名的编译器提供。对于 C++03 编译器,名称将必须更改为 endian_arithmetic

为了支持向后标头兼容性,已弃用的标头 boost/endian/endian.hpp 转发到 boost/endian/arithmetic.hpp。它需要定义 BOOST_ENDIAN_DEPRECATED_NAMES。在过渡到库的官方 Boost 版本时才应使用它,因为它将在未来的某个版本中删除。

未来方向

标准化。

计划将 Boost.Endian 提交给 C++ 标准委员会,以便可能包含在技术规范或 C++ 标准本身中。

numeric_limits 的特化。

Roger Leigh 要求所有 boost::endian 类型都提供 numeric_limits 特化。请参见 GitHub issue 4

字符缓冲区支持。

Peter Dimov 在迷你评审期间指出,从/向 unsigned char 数组中的偏移量获取和设置基本算术类型(或 <cstdint> 等效类型)是常见的需求。请参见 Boost.Endian 迷你评审帖子

超出范围检测。

Peter Dimov 在迷你评审期间建议,对于超出范围的缓冲区值,抛出异常可能是可取的。请参见 此帖子 的结尾和后续回复。

致谢

收到了来自 Adder、Benaka Moorthi、Christopher Kohlhoff、Cliff Green、Daniel James、Dave Handley、Gennaro Proto、Giovanni Piero Deretta、Gordon Woodhull、dizzy、Hartmut Kaiser、Howard Hinnant、Jason Newton、Jeff Flinn、Jeremy Maitin-Shepard、John Filo、John Maddock、Kim Barrett、Marsh Ray、Martin Bonner、Mathias Gaunard、Matias Capeletto、Neil Mayhew、Nevin Liber、Olaf van der Spek、Paul Bristow、Peter Dimov、Pierre Talbot、Phil Endecott、Philip Bennefall、Pyry Jahkola、Rene Rivera、Robert Stewart、Roger Leigh、Roland Schwarz、Scott McMurray、Sebastian Redl、Tim Blechmann、Tim Moore、tymofey、Tomas Puverle、Vincente Botet、Yuval Ronen 和 Vitaly Budovsk 的评论和建议。如果有人被遗漏,敬请谅解。

文档由 Glen Fernandes 转换为 Asciidoc 格式。

此文档是

  • 版权所有 2011-2016 Beman Dawes

  • 版权所有 2019 Peter Dimov