Boost C++ 库

...世界上最受尊敬和设计最精良的 C++ 库项目之一。 Herb SutterAndrei Alexandrescu, C++ 编码标准

概述

摘要

Boost.Endian 提供了用于操作整数和用户定义类型的字节序的功能。

  • 支持三种字节序处理方法。每种方法都有其长期成功使用的历史,并且每种方法都有其相对于其他方法的首选使用场景。

  • 主要用途

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

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

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

字节序简介

考虑以下代码

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)允许操作系统选择应用哪个顺序。

最高有效字节优先的排序传统上称为“大端”排序,而最低有效字节优先的排序传统上称为“小端”排序。这些名称源自 乔纳森·斯威夫特的讽刺小说格列佛游记,其中竞争的王国在不同的末端打开他们煮软的鸡蛋。

有关字节序的详细讨论,请参阅 Wikipedia 的 字节序 文章。

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

Boost.Endian 库简介

Boost.Endian 提供了三种不同的方法来处理字节序。所有三种方法都支持整数和用户定义类型 (UDT)。

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

字节序转换函数

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

字节序缓冲区类型

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

字节序算术类型

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

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

内置的 Intrinsic 支持

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

定义宏 BOOST_ENDIAN_NO_INTRINSICS 将会抑制内在函数的使用。当编译器没有内在函数支持或无法找到相应的头文件时(可能是因为它是较旧的版本或支持库非常有限),这很有用。

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

性能

考虑这个问题

示例 1

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

字节序算术类型方法 字节序转换函数方法
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 ...

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

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

示例 2

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

字节序算术类型方法 字节序转换函数方法
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 ...

对于字节序算术方法,在小端平台上,从大端到大端的隐式转换在循环内完成。对于字节序转换函数方法,用户确保转换在循环外完成,因此代码在小端平台上可能会运行得更快。

计时

这些测试是在 Windows 7 下,在 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 等。

测试用例 字节序算术类型 字节序转换函数

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 等。

测试用例 字节序算术类型 字节序转换函数

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。

该实现是否使用编译器内在的内置字节交换?

是的,如果可用的话。请参阅 内在内置支持

为什么要关心字节序?

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

字节序在可移植二进制文件或网络 I/O 格式之外还有其他用途吗?

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

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

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

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

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

大端往往在网络环境中更受青睐,并且是更多的行业标准,但是对于主要在 x86、x86-64 和其他小端 CPU 上运行的应用程序,小端可能是首选。 Wikipedia 文章提供了更多优点和缺点。

为什么只支持大端和小端本机字节序?

这些是当今唯一具有实际价值的字节序方案。PDP-11 和其他中间字节序方法是很有趣的奇特之处,但对于今天的 C++ 开发人员来说没有意义。允许运行时字节序切换的架构也是如此。 本机排序规范 经过精心设计,以便在未来需要时支持此类排序。感谢 Howard Hinnant 的建议。

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

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

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

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

整数支持有哪些限制?

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

是否支持浮点数?

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

此后,重新为 endian_bufferendian_arithmetic 和就地反转字节序的转换函数恢复了对 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 版本变更

  • 使 endian_reverseconditional_reverse*_to_* 在 GCC 和 Clang 上成为 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

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

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

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

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

背景

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

特性

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

字节序不变量

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

例如

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 转换为本机字节序,因为未使用该成员。后来的维护人员需要将 data.v3 传递给第三方函数,因此在代码的深处添加了 third_party::func(data.v3);。这会导致静默失败,因为类型为 int32_t 的对象按 C++ 核心语言描述的方式保持值的通常不变量不适用。

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

这是使用字节序算术类型的相同示例

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),它将正常工作。

转换显式性

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

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

算术运算

字节序转换函数不提供算术运算,但这并不是一个问题,因为此方法使用普通 C++ 算术类型来保存值。

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

字节序算术类型确实提供算术运算。如果涉及大量算术运算,它们非常易于使用。

大小

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

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

对齐

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

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

像这样的不可移植代码

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

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

struct S {
  big_uint16_ut a;
  big_uint32_ut b;
};

设计模式

应用程序通常以包含多个字节序数据元素的记录或数据包的形式传输字节序数据。为了简单起见,我们只称它们为记录。

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

仅在需要时转换(即延迟)

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

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

预见需要时转换

此模式在预期使用的情况下执行转换为本机字节序,例如在读取记录后立即执行。如果需要,在所有可能的需求都过去之后执行转换为输出字节序,例如在写入记录之前。

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

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

仅在需要时转换,但在本地预见需要时除外

此模式通常会延迟转换,但对于特定的本地需求,则会进行预期的转换。尽管特别适合与字节序缓冲区或算术类型结合使用,但它也适用于转换函数。

示例

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);

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

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

答案:VC++ 2015 预览版,可能还有其他版本,即使对于一个玩具测试程序也没有这样做。尽管节省量很小(两个寄存器 bswap 指令),但如果循环重复足够多次,成本可能会很大。另一方面,程序可能被 I/O 时间所支配,以至于即使是一个冗长的循环也无关紧要。

用例示例

移植不感知字节序的代码库

现有代码库在大字节序系统上运行。它当前不处理字节序。需要修改代码库,以便它可以在各种操作系统下的的小字节序系统上运行。为了简化过渡并保护现有文件的值,外部数据将继续以大字节序维护。

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

移植感知字节序的代码库

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

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

可靠性和算术速度

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

字节序缓冲区方法非常适合此用例。

可靠性和易用性

需要开发一个新的、复杂的、多线程的应用程序,该应用程序必须在小端机器上运行,但执行大端网络 I/O。开发人员认为字节序变量的计算速度并不关键,但由于无法推理字节序转换状态,因此出现了很多错误。他们还关心在开发和长期维护过程中的易用性。

消除对转换速度的担忧并增加对易用性的关注,这使得字节序算术方法更具优势。

字节序转换函数

简介

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

参考

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

定义

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

注意
这些名称源于乔纳森·斯威夫特的讽刺小说格列佛游记,其中对立的王国在不同的末端打开他们的半熟鸡蛋。维基百科对字节序有广泛的描述。

标准整数类型(C++std [basic.fundamental]),bool 和作用域枚举类型(C++std [dcl.enum])除外,统称为字节序类型。在不存在填充位的情况下(这在 Boost.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 是字节序类型或类类型。

如果 T 是字节序类型,则返回字节顺序反转的 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() 进行不限定的调用来执行字节序反转。

如果需要,则需要 EndianReversibleInplace 的库函数模板通过对 endian_reverse_inplace() 进行不限定的调用来执行字节序反转。

有关示例用户定义类型,请参阅 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)

    备注

    是否返回 xendian_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 == order2endian_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 是否与本机字节序匹配以正向或反向顺序读取,将生成的位模式解释为类型 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

    效果

    按照 Order 是否与本机字节序匹配以正向或反向顺序,将 v 的对象表示形式的 N 个最低有效字节写入到 p

便捷加载函数

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 ).

常见问题解答

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

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

按值返回结果是 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 位整数的内在内置实现方法,David Stone 提供了他的 Boost 许可宏实现,该实现成为 boost/endian/detail/intrinsic.hpp 的起点。Pierre Talbot 提供了 int8_t endian_reverse() 和模板化的 endian_reverse_inplace() 实现。

字节序缓冲区类型

简介

算术类型的内部字节顺序传统上称为字节序。请参阅维基百科以全面了解字节序,包括大端序小端序的定义。

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

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

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 字节(对齐)

  • 值类型的选择

枚举和类型定义

提供了两个带作用域的枚举

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;

类型定义,例如 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

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

警告
使用对齐类型的代码可能不具有可移植性,因为对齐要求在硬件架构之间有所不同,并且对齐方式可能会受到编译器开关或编译指示的影响。例如,在 32 位计算机上,64 位整数的对齐方式可能是 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;
    返回

    is.

常见问题解答

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

为什么不直接使用 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 - 换句话说,必须是可内存复制的。

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

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

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

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

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

编译

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

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

字节序算术类型

简介

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

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

这种整数字节持有者类型传统上称为字节序类型。请参阅维基百科以全面了解字节序,包括大端序小端序的定义。

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 字节(对齐)

  • 值类型的选择

枚举和类型定义

提供了两个带作用域的枚举

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;

类型定义,例如 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

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

警告
使用对齐类型的代码可能不具有可移植性,因为对齐要求在硬件架构之间有所不同,并且对齐方式可能会受到编译器开关或编译指示的影响。例如,在 32 位计算机上,64 位整数的对齐方式可能是 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;
    返回

    is.

常见问题解答

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

为什么不直接使用 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 - 换句话说,必须是可内存复制的。

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

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

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

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

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

经验

具有类似功能的类已被多个 Boost 程序员独立开发,并在高价值、高使用率的应用程序中成功使用了多年。这些独立开发的大小端库通常是从也被广泛使用的 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 类模板,他将库组合在一起,提供文档,添加了 typedef,还添加了 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::big 还是 order::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 问题 4

字符缓冲区支持。

Peter Dimov 在迷你评审期间指出,从/到无符号字符数组的偏移量获取和设置基本算术类型(或 <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