reading/writing 来自 ostringstream 更改原始数据

reading/writing from ostringstream changes original data

在测试中,我将给定值写入 ostringstream。之后,我尝试评估是否写入了正确的值。但是,我写入流的原始值似乎在稍后从流中读取时发生了变化。

我将我的问题简化为以下代码:

#include <sstream>
#include <cassert>
#include <iostream>

int main()
{
    std::ostringstream os;
    uint16_t data{ 123 };
    os.write(reinterpret_cast<const char*>(&data), sizeof(uint16_t));

    uint16_t data_returned;
    std::string data_str(os.str());
  
    std::copy(data_str.begin(), data_str.end(), &data_returned);

    assert(data == 123); // <- not true
    assert(data_returned == 123);
}

此处 data 似乎已被 std::copy() 更改(变为 0)。

我也把代码放在godbolt上:https://godbolt.org/z/bcb4PE

更奇怪,如果我把uint16_t data{ 123 };改成const uint16_t data{ 123 };,就没问题了

似乎我缺少一些关于 std::copy() 机制的见解。

在这种情况下,

std::copy() 并不像您认为的那样。它只是逻辑循环的包装器,将 individual 元素从一个容器复制到另一个容器。您实际上是在告诉它从 data_str 复制 2 个 charsuint16_t[2] 数组,只是您实际上没有这样的数组,所以您有 undefined行为 并且正在破坏堆栈内存。

这条语句:

std::copy(data_str.begin(), data_str.end(), &data_returned);

基本上是这样做的:

std::string::iterator iter = data_str.begin(), end = data_str.end();
uint16_t *dest = &data_returned;
while (iter != end) {
    *dest++ = *iter++;
}

这基本上等同于这样做,在您的示例中:

uint16_t *dest = &data_returned;
dest[0] = static_cast<uint16_t>(data_str[0]);
dest[1] = static_cast<uint16_t>(data_str[1]);

它将第一个字节分配给整个uint16_t(这就是你看到值变化的原因),然后它正在分配第二个bytenext 整个 uint16_t (破坏堆栈)。

对于您的尝试,请改用 std::memcpy(),例如:

std::memcpy(&data_returned, data_str.c_str(), sizeof(data_returned));

否则,如果你真的想使用std::copy(),你需要确保它知道将单个字节复制到目的地,而不是整个uint16_ts,例如:

std::copy(data_str.begin(), data_str.end(), reinterpret_cast<char*>(&data_returned));

由于输入和输出都是普通类型,因此应该优化为适当的 std::memcpy() 等效副本。

为了以这种方式使用 std::copy(),您需要将 &data_returned 转换为 char*,否则 std::copy() 会将其视为 uint16_t* ] 并填充两个 uint16_t,而不是您想要的两个 char。这使您的程序具有 undefined behavior,这就是您在 data 中看到更改值的原因。该程序也可能崩溃或做了完全不同的事情。

std::copy(data_str.begin(), data_str.end(), reinterpret_cast<char*>(&data_returned));

这是你的程序编译后有两个额外的选项(-ggdb -fsanitize=address),以便将来更容易发现类似这样的问题:Demo