Boost C++ 库

...世界上最受推崇和设计最精良的 C++ 库项目之一。 Herb SutterAndrei 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 或带有 Intel CPU 的 Windows 系统上,对 "test.bin" 输出文件的十六进制转储会产生

0201

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

0102

这里发生的是 Intel CPU 按最低有效字节优先的顺序排列整数的字节,而 SPARC CPU 按最高有效字节优先的顺序排列。一些 CPU(如 PowerPC)允许操作系统选择应用哪种排序。

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

有关字节序的详尽讨论,请参阅维基百科的字节序文章。

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

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 获取构建设置。

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

迭代次数: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 上运行的应用程序。维基百科的文章提供了更多的优点和缺点。

为什么只支持大端和小端原生字节序?

这些是今天唯一有实际价值的字节序方案。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)浮点数,并且进一步限于浮点字节序与整数字节序不同的系统。即使有这些限制,对浮点类型的支持也不可靠,已被移除。例如,简单地反转浮点数的字节序可能导致一个信号 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 版本变更

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

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

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

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

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

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

1.71.0 版中的更改

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

  • endian_bufferendian_arithmetic 添加了对 floatdouble 的支持。

  • 添加了 endian_loadendian_store

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

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

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

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

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

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

背景

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

特性

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

字节序不变性

字节序转换函数使用普通的 C++ 算术类型(如 intunsigned short)的对象来保存值。这打破了 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 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 架构同时支持大端和小端排序。

注意
这些名称来源于乔纳森·斯威夫特的讽刺小说格列佛游记,其中两个敌对王国从不同的末端打开他们的煮鸡蛋。维基百科上有关于字节序的详尽描述。

标准整型(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,所以用户定义的类型只需要满足 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)

    备注

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

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

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

原地字节反转函数

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

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

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

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

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

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

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

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

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

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

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

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

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

    如果 O1 == O2,则不执行任何操作,否则调用 endian_reverse_inplace(x)

    备注

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

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

    如果 order1 == order2,则调用 endian_reverse_inplace(x)

通用加载和存储函数

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

    sizeof(T) 必须是 1、2、4 或 8。N 必须在 1 到 sizeof(T)(包括)之间。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 进一步改进了这一点。多个审阅者以及 David Stone 提出了 16、32 和 64 位整数的内在内置函数实现方法,David Stone 提供了他的 Boost 许可宏实现,该实现成为了 boost/endian/detail/intrinsic.hpp 的起点。Pierre Talbot 提供了 int8_t endian_reverse() 和模板化的 endian_reverse_inplace() 实现。

字节序缓冲区类型

介绍

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

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

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

endian_buffer 类面向希望显式控制字节序转换发生时间的用​​户。它也作为 endian_arithmetic 类模板的基类,后者面向希望完全自动进行字节序转换并直接支持所有正常算术运算的用​​户。

示例

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

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

using namespace boost::endian;

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

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

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

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

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

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

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

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

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

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

  std::fclose(fi);

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

  return 0;
}

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

01020304 00000010 01000000 04030201

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

限制

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

功能集

  • 大端序 | 小端序 | 原生字节序。

  • 有符号 | 无符号

  • 未对齐 | 已对齐

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

  • 值类型选择

枚举和类型定义

提供了两个作用域枚举

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

big

有符号

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

big_uintN_buf_t

big

无符号

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

little_intN_buf_t

little

有符号

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

little_uintN_buf_t

little

无符号

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

big

有符号

8,16,32,64

big_uintN_buf_at

big

无符号

8,16,32,64

little_intN_buf_at

little

有符号

8,16,32,64

little_uintN_buf_at

little

无符号

8,16,32,64

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

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

    .

常见问题

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

为什么不直接使用 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 提供了整数二进制类型,可控制字节顺序、值类型、大小和对齐。类型别名(Typedefs)为常见配置提供了易于使用的名称。

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

此类整数字节持有者类型传统上称为 **endian** 类型。有关 **endianness** 的完整探讨,包括 **big endian**(大端序)和 **little endian**(小端序)的定义,请参阅 Wikipedia

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

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

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

示例

example/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

big

有符号

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

big_uintN_t

big

无符号

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

little_intN_t

little

有符号

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

little_uintN_t

little

无符号

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

big

有符号

8,16,32,64

big_uintN_at

big

无符号

8,16,32,64

little_intN_at

little

有符号

8,16,32,64

little_uintN_at

little

无符号

8,16,32,64

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

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

    .

常见问题

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

为什么不直接使用 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 的时间测量endian/test 目录还包含几个额外的基准测试和速度测试程序。

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

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

注意
此答案已过时。floatdouble 的支持后来发现存在问题并已被移除。最近,对 endian_bufferendian_arithmeticfloatdouble 支持已恢复,但对转换函数不支持。
用户定义类型 (UDTs) 的支持是可取的,并且应在不与其他问题冲突的情况下提供。

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

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

完成。字节序类型已分解为类模板 endian_buffer 和类模板 endian_arithmeticendian_buffer 类是 endian_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 在迷你审查期间指出,从 unsigned char 数组的偏移量获取和设置基本算术类型(或 <cstdint> 等价物)是一个常见的需求。请参阅 Boost.Endian 迷你审查帖子

超出范围检测。

Peter Dimov 在迷你审查期间建议,在缓冲区值超出范围时抛出异常可能是可取的。请参阅此帖子末尾及其后的回复。

致谢

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

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

本文档

  • 版权所有 2011-2016 Beman Dawes

  • 版权所有 2019 Peter Dimov