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 个 chars
到 uint16_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
(这就是你看到值变化的原因),然后它正在分配第二个byte 到 next 整个 uint16_t
(破坏堆栈)。
对于您的尝试,请改用 std::memcpy()
,例如:
std::memcpy(&data_returned, data_str.c_str(), sizeof(data_returned));
否则,如果你真的想使用std::copy()
,你需要确保它知道将单个字节复制到目的地,而不是整个uint16_t
s,例如:
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
在测试中,我将给定值写入 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 个 chars
到 uint16_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
(这就是你看到值变化的原因),然后它正在分配第二个byte 到 next 整个 uint16_t
(破坏堆栈)。
对于您的尝试,请改用 std::memcpy()
,例如:
std::memcpy(&data_returned, data_str.c_str(), sizeof(data_returned));
否则,如果你真的想使用std::copy()
,你需要确保它知道将单个字节复制到目的地,而不是整个uint16_t
s,例如:
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