【问题标题】:std::cout not properly printing std::string created by reinterpret_cast of unsigned char arraystd::cout 未正确打印由 unsigned char 数组的 reinterpret_cast 创建的 std::string
【发布时间】:2014-08-28 10:34:53
【问题描述】:

我有一个存储 1 字节十六进制字符的 unsigned char 数组,我想对这些值进行按位运算。

在使用 reinterpret_cast 将它们变成一个字符串后(考虑使用 std::stringstream 和 std::bitset 进行必要的操作),我尝试打印字符串以查看内容是什么。相当奇怪的是,我注意到 std::cout 没有给出预期的结果,但是使用 printf 做到了!

这是一个简单的例子:

int main(int argc, char *argv[])
{

    unsigned char my_txt[] = {
        0x52, 0x5f, 0x73, 0x68, 0x7e, 0x29, 0x33, 0x74, 0x74, 0x73, 0x72, 0x55
    };
    unsigned int my_txt_len = 12;

    std::string my_std_string(reinterpret_cast<const char *>(my_txt), my_txt_len);

    for (size_t i=0;i<my_txt_len;i++)
        printf("%02X ", my_std_string[i]);      // Works fine!
    printf("\n");

    std::cout << my_std_string << std::endl;    // Bad stuff happens :S

    return 0;
}

有输出:

52 5F 73 68 7E 29 33 74 74 73 72 55     // Expected
R_sh~)3ttsrU                            // ??

决心找到解决方案,我修补了一段时间 - 猜测 reinterpret_cast 可能会导致这种行为。我最终发现这样做:

std::cout << std::hex << (int)my_std_string[0] << std::dec << std::endl;

产生了预期的结果,至少对于第一个字符。遍历一个循环也为其他 11 个字节提供了正确的值。

有人可以解释为什么 std::cout 会发生这种情况而不是 printf 吗?起初,我认为也许我需要将其转换回unsigned char,但这样做没有任何效果。为什么转换为 int 会给出正确的输出?

在使用 reinterpret_cast 之后对存储在字符串中的值进行按位运算是否安全?我突然想到,所有这些都可能毫无意义,因为我相信我可以直接对 unsigned char 进行二进制数学运算,不是吗?我们将不胜感激。

对于那些好奇的人,我正在尝试编写一个自定义 C++ 控制台应用程序(Windows 7 64 位计算机上的 Microsoft Visual Studio 2010),以使用供应商的 API 与 CAN-USB 适配器接口。我希望接收(作为较大的“接收帧”结构的一部分)8 字节无符号字符数组,其值为十六进制,我需要处理这些值以获得我的应用程序可用的数据。然后将处理后的数据存储在协议缓冲区中,以便在 matlab 中进一步解释。

抱歉,这似乎是一个愚蠢的问题 - 我来自硬件背景,并且有一段时间没有进行任何认真的编程(SO 上的第一篇文章!)。

【问题讨论】:

  • 您期望的字符串是什么?根据 ascii 表,这似乎是正确的。
  • 您不存储“1 字节十六进制字符”。您存储字节。您在文字中写入以将这些值输入程序源代码的数字的基数不相关。此外,您的 printf 和 std::cout 方法甚至不完全相同,因此抱怨它们有不同的结果似乎对所讨论的编程语言有些不公平。
  • 您混淆了表示和值。您可能希望查看与此问题相关的twoquestions。简而言之,52 5F 73 68 7E 29 33 74 74 73 72 55 和R_sh~)3ttsrU 都是{ 0x52, 0x5f, 0x73, 0x68, 0x7e, 0x29, 0x33, 0x74, 0x74, 0x73, 0x72, 0x55 }; 字节序列的有效表示。
  • 感谢@LightnessRacesinOrbit 和milleniumbug。是的,看来我对基础知识有点生疏了哈哈所以我认为对存储在字符串中的值进行二进制数学运算(位掩码、将字节相加等)是安全的?
  • @KnightsValour:当然为什么不呢?

标签: c++ cout stdstring reinterpret-cast


【解决方案1】:

我正在切换到 Python,只是为了展示:

>>> s = [ 0x52, 0x5f, 0x73, 0x68, 0x7e, 0x29, 0x33, 0x74, 0x74, 0x73, 0x72, 0x55]
>>> ''.join(map(chr, s))
'R_sh~)3ttsrU'

我的意思是,这些是您的字节的 ASCII 等价物。这就是 std::string 的构造函数所做的:获取一个以 nul 结尾的字符数组,并从那里构建一个字符串。您的reinterpret_cast 在unsigned char* 和char* 之间转换,这是它所做的为数不多的安​​全操作之一。

您可能想要的是用整数的文本表示构建一个字符串。为此使用std::ostringstream:

std::ostringstream os;
os << std::hex << std::setfill('0') << std::uppercase;
for (size_t i=0;i<my_txt_len;i++)
    os << std::setw(2) << my_txt[i] << " ";
std::string txt = os.str();

std::cout << txt;

使用printf,您没有这个问题,因为printf 中的参数类型由格式字符串设置,在您的情况下,%X 表示读取为整数并写入十六进制。如果您传递 char/unsigned char,它会自动提升为整数(但请注意!大多数类型不会以这种方式提升,只有 chars 和 shorts)。

【讨论】:

  • 感谢详细的解释和代码!我想输出对我来说并不是绝对必要的——因为我只需要确保使用 reinterpret_cast 正确地将值放入字符串中。只要我可以对这些值进行二进制数学运算然后存储它们——我想我会没事的。但是,我会保留您的代码作为参考。
  • @KnightsValour:没问题。但是如果你想存储unsigned chars,可以考虑使用std::vector&lt;unsigned char&gt;。 std::string 用于文本,而不是原始字节,它迟早会给你带来麻烦。
  • 感谢您的建议。为了澄清,我正在使用供应商的库,该库提供结构作为消息帧的容器。在此结构中,数据字节存储在具有以下声明的数组中:uint8_t data[8]。鉴于此,我需要能够对字节进行二进制数学运算,并且其他 SO 帖子已经提到使用 std::stringstream 和 std::bitset。这就是我最初想到使用 std::string 作为容器的原因——因为最终,这些信息将使用协议缓冲区进行序列化。有没有更好的方法可以满足我的要求?
【解决方案2】:

改变

std::cout << my_std_string << std::endl;    // Bad stuff happens :S

到

for( std::size_t i = 0; i < my_txt_len ; i++ )
{
    std::cout << std::hex << static_cast<unsigned>(my_std_string[i]) << " " ;
}
std::cout << std::endl;

std::string 是字符串的表示,而不是纯粹的字节数组。因此,将其传递给std::cout 将显示一个字符串。您的 printf 正在打印您的 unsigned char 数组的各个值。与此等效的 stl 是 std::vector&lt;unsigned char&gt;。

您需要添加static_cast&lt;unsigned&gt;()。否则std::cout 会将每个unsigned char 值打印为char ascii 字符。输出将是R _ s h ~ ) 3 t t s r U。你必须通过隐式告诉它来防止这种转换。

【讨论】:

  • 感谢您的解释,@tillaert!您能解释一下为什么需要将其转换为 int(而不是无符号类型)吗?
  • 似乎 static_casting 作为 或 产生相同的答案。好吧,只要可以安全地操作该值,我想我就可以了。再次感谢!
【解决方案3】:

有人可以解释为什么 std::cout 会发生这种情况而不是 printf 吗?起初,我想也许我需要将它转换回 unsigned char 但这样做没有任何效果。为什么转换为 int 会给出正确的输出?

您告诉printf 以十六进制输出,所以它以十六进制输出。究竟有什么奥秘?

【讨论】:

  • 正确 - 我认为在 cout 语句中添加 std::hex 会做同样的事情。正如@tillaert 指出的那样,字符串不是字节数组,我似乎期待它是。感谢您的澄清!
猜你喜欢
  • 2010-10-22
  • 2012-11-04
  • 2012-03-07
  • 2012-05-13
  • 2013-07-18
  • 2011-07-23
  • 2011-10-26
  • 2011-02-19
  • 1970-01-01
相关资源
最近更新 更多