19

互联网上有几篇文章建议您应该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>.

使用二进制数据唯一潜在的剩余问题(我可以看到)std::stringstd::string使用char(可以签名)。

char, signed char, 和unsigned char是三种不同的类型,char可以是无符号的或有符号的。

11111111b因此,当从 char 返回的实际字节值std::string:operator[],并且您想检查它的值时,它的值可以是255(如果char是无符号的)或者它可能是“负数”(如果char是有符号的,取决于您的数字表示)。

类似地,如果您想将实际字节值显式附加11111111b到 a std::string,则简单地附加(char) (255)可能是实现定义的(甚至引发信号)如果char已签名并且inttochar对话导致溢出。

那么,有没有一种安全的方法来解决这个问题,再次使std::string二进制安全?

§3.10/15 规定:

如果程序尝试通过非下列类型之一的泛左值访问对象的存储值,则行为未定义:

  • [...]
  • 与对象的动态类型相对应的有符号或无符号类型,
  • [...]
  • char 或 unsigned char 类型。

如果我理解正确的话,它似乎允许使用unsigned char*指针来访问和操作 a 的内容,std::string并使 this 也是定义良好的. 它只是将位模式重新解释为,而没有任何更改或信息丢失,后者即因为 a 、和unsigned char中的所有位都必须用于值表示。charsigned charunsigned char

然后,我可以使用unsigned char*对内容的这种解释std::string作为访问和更改[0, 255]范围内字节值的一种方式,以一种明确定义和可移植的方式,而不管其char自身的符号性如何。

这应该可以解决由潜在签名引起的任何问题char

我的假设和结论正确吗?

此外,unsigned char*相同位模式(即11111111b10101010b)的解释是否保证在所有实现中都相同?换句话说,标准是否保证“通过眼睛看unsigned char”,相同的位模式总是导致相同的数值(假设一个字节中的位数相同)?

因此,我可以安全地(即,没有任何未定义或实现定义的行为)std::string用于在 C++11 中存储和操作二进制数据吗?

4

3 回答 3

18

static_cast<char>(uc)其中ucis 类型的转换unsigned char始终有效:根据 3.9.1 [basic.fundamental] 的表示charsigned charunsigned char与与char其他两种类型之一相同:

声明为字符 (char) 的对象应足够大以存储实现的基本字符集的任何成员。如果该集合中的字符存储在字符对象中,则该字符对象的整数值等于该字符的单个字符文字形式的值。char 对象是否可以保存负值是实现定义的。字符可以显式声明为无符号或有符号。普通字符、有符号字符和无符号字符是三种不同的类型,统称为窄字符类型。char、signed char 和 unsigned char 占用相同的存储量并具有相同的对齐要求(3.11);也就是说,它们具有相同的对象表示。对于窄字符类型,对象表示的所有位都参与值表示。对于无符号窄字符类型,值表示的所有可能的位模式都表示数字。这些要求不适用于其他类型。在任何特定实现中,普通 char 对象可以采用与带符号字符或无符号字符相同的值;哪一个是实现定义的。

当然,将值转换为范围之外的值unsigned charchar产生问题,并且可能导致未定义的行为。也就是说,只要你不尝试将有趣的值存储到std::string你就可以了。关于位模式,您可以依靠nth 位转换为 2 nstd::string仔细处理后,将二进制数据存储在 a 中应该没有问题。

也就是说,我不相信你的前提:处理二进制数据主要需要处理最好使用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;
于 2013-11-03T21:01:07.193 回答
1

是的,你的假设是正确的。将二进制数据作为无符号字符序列存储在 std::string 中。

于 2013-11-03T20:46:16.877 回答
-2

我在 Microsoft Visual Studio 中使用 std::string 处理二进制数据时遇到了麻烦。我已经看到字符串被莫名其妙地截断了,所以无论标准文档怎么说,我都不会这样做。

于 2015-01-05T00:37:23.060 回答