Boost C++ 库

one of the most highly regarded and expertly designed C++ library projects in the world. Herb Sutter and Andrei Alexandrescu, C++ Coding Standards

Boost.Endian:Boost Endian 库 - Boost 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);

在 OS X、Linux 或 Windows 的 Intel CPU 系统上,对 "test.bin" 输出文件的十六进制转储结果为

0201

在 OS X 的 PowerPC CPU 系统或 Solaris 的 SPARC CPU 系统上,对 "test.bin" 输出文件的十六进制转储结果为

0102

这里发生的情况是,Intel CPU 按低位字节优先的顺序排列整数的字节,而 SPARC CPU 则按高位字节优先的顺序排列。有些 CPU,如 PowerPC,允许操作系统选择哪种排序方式适用。

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

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

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

Boost.Endian 库简介

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

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

字节序转换函数

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

字节序缓冲区类型

应用程序使用提供的字节序缓冲区类型来存储值,并显式地转换为内置整数类型和从内置整数类型转换。提供了 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。

内置对内部函数的支持

大多数编译器,包括 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 格式之外还有其他用途吗?

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

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

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

  • 缺点,例如无法在结果文件上使用文本工具,会将实用性限制在二进制 I/O 优势至关重要的应用程序。

哪种字节序更好,大端还是小端?

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

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

这些是今天唯一具有实际价值的字节序方案。PDP-11 和其他中间字节序方法是令人好奇的,但与当今的 C++ 开发人员无关。对于允许运行时字节序切换的架构也是如此。根据 Howard Hinnant 的建议,本机排序规范 经过精心设计,以便将来需要时可以支持此类排序。

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

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

使用缓冲区类型而不是一直使用算术类型有什么好处?

保证不会执行隐藏的转换。这对于关心达到最快速度的用户来说至关重要。“一直只使用算术类型”对于其他用户来说也很好。当需要确保最快的速度时,算术类型可以用于与缓冲区类型相同的 设计模式 或习惯用法,从而生成与两种类型相同的代码。

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

测试仅在使用二补码算术的机器上进行。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)浮点,并且进一步限于浮点字节序与整数字节序不不同的系统。即使有这些限制,浮点类型支持也不可靠,并已被删除。例如,简单地反转浮点数的字节序可能导致一个信令 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

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

  • 添加了浮点便利类型别名。

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

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

  • 添加了一个方便的头文件 boost/endian.hpp

1.71.0 版中的更改

  • 澄清了对值类型模板参数的要求。

  • endian_bufferendian_arithmetic 添加了对 floatdouble 的支持。

  • 添加了 endian_loadendian_store

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

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

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

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

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

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

背景

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

特性

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

字节序不变量

字节序转换函数 使用普通 C++ 算术类型(如 intunsigned short)的对象来存储值。这打破了 C++ 语言规则适用的隐含不变量。通常的语言规则仅在对象的字节序当前设置为平台的本机字节序时适用。这会使逻辑流程非常难以推理,并导致难以发现的 bug。

例如:

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 Preview,以及可能其他的,即使是对于一个玩具测试程序,也不会这样做。尽管节省的量很小(两个寄存器 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 架构支持大端和小端排序。

注意
这些名称源自 乔纳森·斯威夫特 的讽刺小说格列佛游记,其中敌对的王国以不同的方式开启他们的煮鸡蛋。Wikipedia 提供了对 Endianness 的详尽描述。

标准整型(C++std [basic.fundamental])除 bool 外,以及作用域枚举类型(C++std [dcl.enum]) collectively 称为字节序类型。在没有填充位的情况下(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,因此用户定义的类型只需要满足 EndianReversibleInplace 要求,只需要提供 endian_reverse。尽管用户定义的类型不需要提供 endian_reverse_inplace 函数,但这样做可能会提高效率。
用户定义类型 (UDTs) 的自定义点

本小节描述了对 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;
  • 效果

    对于从 0N-1i,调用 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) 之间(含)。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) 之间(含)。T 必须是平凡可复制的。如果 N 不等于 sizeof(T)T 必须是整数或 enum

    效果

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

便利加载函数

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 提出了进一步的改进。内部内建实现方法,用于 16、32 和 64 位整数,由多位审阅者以及 David Stone 提出,他提供了他的 Boost 许可宏实现,该实现成为 boost/endian/detail/intrinsic.hpp 的起点。Pierre Talbot 提供了 int8_t endian_reverse() 和模板化的 endian_reverse_inplace() 实现。

字节序缓冲区类型

介绍

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

头文件 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

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

注意
使用对齐类型的代码可能不具有可移植性,因为不同硬件架构的对齐要求不同,并且对齐可能受编译器开关或 pragma 的影响。例如,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;
    返回

    .

常见问题

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

为什么不直接使用 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 - 换句话说,必须是 memcpyable。

  • 必须提供与指定的尺寸和内部字节顺序完全一致。

  • 当内部整数表示比外部字节表示的总位数更多时,也必须正确工作。当内部整数表示类型的位数多于外部字节位数之和时,符号扩展必须正确工作。例如,在内部使用 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,无论是通过文件还是网络连接。虽然数据可移植性是主要动机,但这些整数字节持有者也可用于减少内存使用、文件尺寸或网络活动,因为它们提供了通常无法获得的二进制整数尺寸。

此类整数字节持有者类型传统上称为字节序类型。有关字节序的完整探讨,包括大端小端的定义,请参阅 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 字节(对齐)

  • 值类型选择

枚举和类型别名

提供了两个作用域枚举

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

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

注意
使用对齐类型的代码可能不具有可移植性,因为不同硬件架构的对齐要求不同,并且对齐可能受编译器开关或 pragma 的影响。例如,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;
    返回

    .

常见问题

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

为什么不直接使用 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 - 换句话说,必须是 memcpyable。

  • 必须提供与指定的尺寸和内部字节顺序完全一致。

  • 当内部整数表示比外部字节表示的总位数更多时,也必须正确工作。当内部整数表示类型的位数多于外部字节位数之和时,符号扩展必须正确工作。例如,在内部使用 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 类模板,他整合了该库,提供了文档,添加了类型别名,并添加了 unrolled_byte_loops 符号特例化,以正确扩展符号,当覆盖整数尺寸与字节序表示尺寸不同时。

附录 A:历史和致谢

历史

正式审查要求的变更

该库被彻底重写,以适应正式审查期间要求的变更。在进行小型审查之前需要解决的问题将在下方以粗体显示,并注明解决方案。

应开发常见用例场景。

完成。文档已被重构。现在有一个页面专门介绍 选择字节序的方法。有关用例场景,请参阅 用例

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

完成。请参阅 选择方法。已在各处添加了示例代码。

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

完成。请参阅 选择方法

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

完成。请参阅 转换函数

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

完成。如请求所示,在实现中适当使用了编译器(Clang、GCC、VisualC++ 等)内部函数和内建函数。有关内部函数的性能影响,请参阅 内置内部函数支持。有关示例 2 的性能,请参阅 示例 2 的计时

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

完成。对于字节序类型,如请求所示,实现使用了字节序转换函数,从而使用了内部函数。

应提供测量性能的基准测试。应能够将平台特定性能增强与可移植的基础实现进行比较,并将字节序整数方法与常见用例场景的字节序转换方法进行比较。

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

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

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

注意
此回答已过时。对 floatdouble 的支持后来被发现有问题并已被删除。最近,对 floatdouble 的支持已恢复到 endian_bufferendian_arithmetic,但尚未恢复到转换函数。
用户定义类型 (UDT) 的支持是可取的,并且只要不与其他关注点冲突就应提供。

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

有人担心字节序整数/浮点算术运算可能会被无意或不恰当地使用。应调查添加一个没有算术运算的 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 后缀。

  • 已为 int8_tuint8_t 添加了 endian_reverse() 重载,以提高通用性。(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 在小型审查期间指出,从无符号 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