我可以安全地将 std::string 用于 C++11 中的二进制数据吗?
Can I safely use std::string for binary data in C++11?
互联网上有几篇帖子建议你应该对二进制数据使用std::vector<unsigned char>
或类似的东西。
但我更喜欢std::basic_string
变体,因为它提供了许多方便的字符串操作函数。而 AFAIK,自 C++11 以来,该标准保证了每个已知的 C++03 实现已经做到的事情:std::basic_string
将其内容连续存储在内存中。
乍一看,std::basic_string<unsigned char>
可能是一个不错的选择。
但是,我不想使用std::basic_string<unsigned char>
,因为几乎所有操作系统功能都只接受char*
,因此需要显式强制转换。此外,字符串文字是const char*
的,因此每次将字符串文字分配给二进制字符串时,我都需要显式强制const unsigned char*
,我也想避免这种情况。此外,用于读取和写入文件或网络缓冲区的函数同样接受char*
和const char*
指针。
这就剩下std::string
,这基本上是std::basic_string<char>
的typedef。
对二进制数据使用 std::string
的唯一潜在剩余问题(我可以看到)是std::string
使用char
(可以签名)。
char
、signed char
和 unsigned char
是三种不同的类型,char
可以是无符号的,也可以是无符号的。
因此,当实际字节值 11111111b
作为 char 从std::string:operator[]
返回时,并且您想要检查其值,其值可以是255
(如果char
是无符号的),也可以是"负数"(如果char
有符号,具体取决于您的数字表示形式)。
同样,如果要显式地将实际字节值11111111b
附加到std::string
,如果char
是有符号的,并且int
char
会话会导致溢出,则只需附加(char) (255)
可能是实现定义的(甚至会引发信号)。
那么,有没有一种安全的方法可以解决这个问题,使std::string
再次成为二进制安全?
§3.10/15 指出:
如果程序尝试通过以下类型之一以外的 glvalue 访问对象的存储值,则行为是未定义的:
- [...]
- 一种类型,该类型是对应于对象的动态类型的有符号或无符号类型,
- [...]
- 字符或无符号字符类型。
如果我理解正确的话,它似乎允许使用 unsigned char*
指针来访问和操作std::string
的内容,并使其也定义良好。它只是将位模式重新解释为unsigned char
,没有任何变化或信息丢失,后者是因为char
、signed char
和unsigned char
中的所有位都必须用于值表示。
然后,我可以使用这种对std::string
内容的unsigned char*
解释作为访问和更改[0, 255]
范围内的字节值的方法,以定义明确且可移植的方式,而不管char
本身的符号性如何。
这应该可以解决由潜在签名char
引起的任何问题。
我的假设和结论是否正确?
此外,也是对相同位模式的unsigned char*
解释(即 11111111b
或 10101010b
) 保证在所有实现上都相同?换句话说,标准是否保证"通过unsigned char
的眼睛看",相同的位模式总是导致相同的数值(假设字节中的位数相同)?
因此,我是否可以安全地(即,没有任何未定义或实现定义的行为)使用 std::string
在 C++11 中存储和操作二进制数据?
uc
类型为 unsigned char
的转换static_cast<char>(uc)
始终有效:根据 3.9.1 [basic.fundamental],char
、signed char
和 unsigned char
的表示形式与char
与另外两种类型之一相同
声明为字符 (char) 的对象应足够大,以存储实现的基本字符集的任何成员。如果此集中的字符存储在字符对象中,则该字符对象的整数值等于该字符的单个字符文本形式的值。char 对象是否可以保存负值是实现定义的。字符可以显式声明为无符号或有符号。纯字符、有符号字符和无符号字符是三种不同的类型,统称为窄字符类型。字符、有符号字符和无符号字符占用相同的存储量,并具有相同的对齐要求 (3.11);也就是说,它们具有相同的对象表示形式。对于窄字符类型,对象表示形式的所有位都参与值表示形式。对于无符号窄字符类型,值表示形式的所有可能的位模式都表示数字。这些要求不适用于其他类型的类型。在任何特定的实现中,纯字符对象可以采用相同的 值为有符号字符或无符号字符;哪一个是实现定义的。
当然,将unsigned char
范围之外的值转换为char
值会带来问题,并可能导致未定义的行为。也就是说,只要你不试图将有趣的值存储到std::string
中,你就没问题了。关于位模式,您可以依靠第 n
位转换为 2 n
。仔细处理时,将二进制数据存储在std::string
中应该没有问题。
也就是说,我不相信你的前提:处理二进制数据主要需要处理最好使用unsigned
值操作的字节。您需要在 char*
和 unsigned char*
之间转换的少数情况在没有明确处理时创建方便的错误,同时意外地弄乱了char
的使用,这将是沉默的!也就是说,处理unsigned char
将防止错误。我也不相信你得到所有这些不错的字符串函数的前提:首先,无论如何,你通常最好使用这些算法,但二进制数据也不是字符串数据。总之:对std::vector<unsigned char>
的建议不是凭空而来的!故意避免在设计中建造难以找到的陷阱!
支持使用 char
的唯一温和合理的论点可能是关于字符串文字的论点,但即使是这样,在 C++11 中引入的用户定义的字符串文字中也站不住脚:
#include <cstddef>
unsigned char const* operator""_u (char const* s, size_t)
{
return reinterpret_cast<unsigned char const*>(s);
}
unsigned char const* hello = "hello"_u;
是的,你的假设是正确的。将二进制数据存储为 std::string 中的无符号字符序列。
我在使用 std::string 处理 Visual Studio 中的二进制数据时遇到了麻烦Microsoft。我已经看到字符串被莫名其妙地截断了,所以无论标准文档怎么说,我都不会这样做。
- 如何从dicom文件中读取二进制数据
- 如何在Qt中从数据库中检索二进制数据?
- readsome() 适合在 Windows 上读取二进制数据吗?
- 如何使用 redis-plus-plus 存储二进制数据,就像我想存储结构一样?@for_stack?
- 将包含二进制数据的 QByteArray 传递到按值运行
- 如何在 c++ 中生成十六进制二进制数据的 sha256 哈希?
- 在处理网络、二进制数据和序列化时应使用流或容器
- 我能确定从文件中读取的 32 字节二进制数据等于 256 位吗?
- C++:如何通过 curl 调用使用 HTTP post 请求发送二进制数据(protobuf 数据)
- 使用二进制数据更新 PostgreSQL 表
- 使用二进制数据和无符号字符
- sd_journal_send发送二进制数据.如何使用日志检索数据?
- 从带有 std::ifstream::read() 的文件中读取 int 遍历 char * 二进制数据
- 将文本和二进制数据连接到一个文件中
- 二进制模式 + 格式化文本操作或文本模式 + 二进制数据操作 - 有意义吗?
- 将整数的二进制数据转换为浮点数
- 使用 CMake 在可执行文件中嵌入二进制数据
- 二进制数据作为命令行参数
- 如何访问文件的二进制数据?
- 返回二进制数据的通用方式,而无需原始指针