sam*_*ise -1 c++ memory-corruption
以下是我能够从更大的代码库中提取的片段,希望能够说明目前我无法看到的某种内存损坏.这是在Ubuntu 17.04上使用g ++ 6.3.0,虽然我在gcc 7.0.1和clang 4.0.0上看到了同样的问题.
#include <array>
#include <assert.h>
using Msg = std::array<char,sizeof(std::string)*2> ;
class Str {
public:
explicit Str (std::string &&v) : v (std::move(v)) {}
std::string v;
};
void f(Msg &tmsg)
{
Msg m;
new (&m) Str ("hello");
tmsg = m;
}
int main( int , char* [] )
{
Msg tmsg;
f(tmsg);
auto ptr = (Str*) &tmsg;
assert(ptr->v == "hello"); // This fails
return 0;
}
Run Code Online (Sandbox Code Playgroud)
当我尝试运行时,我得到:
$ g++ main.cpp -g -std=c++11 && ./a.out
a.out: main.cpp:24: int main(int, char**): Assertion `ptr->v == "hello"' failed.
Aborted
Run Code Online (Sandbox Code Playgroud)
有任何想法吗?我一直在盯着这几个小时,我一直无法理解.
根据C++标准,此代码不合法.有很多问题:
对准.您无法确保a的存储Str与同一边界对齐std::string,因此您的代码具有未定义的行为,无需诊断.它std::aligned_storage_t比std::array你喜欢的更简单.
您试图std::string通过复制底层字节来复制周围.这不合法,标准不会授予您许可.它违反了C++中非平凡类类型的基本生存期要求,并且在这种情况下违反了严格的别名规则.
在这个功能中,坏事正在发生
void f(Msg &tmsg)
{
Msg m;
new (&m) Str ("hello");
tmsg = m;
}
Run Code Online (Sandbox Code Playgroud)
什么时候tmsg = m发生 这是底层字节获取副本的时候,但这不是你可以安全地复制对象的方式.如果它非常重要,比如std :: string,并拥有像堆分配缓冲区这样的资源,则需要调用复制构造函数,否则类不能强制执行其保证.(该行本身不会导致未定义的行为,但是当您尝试将tmsg字节重新解释为有效的Str时,即UB.)
另请注意,因为您使用了placement new,并且您从未在任何地方调用过dtor,所以您正在泄漏您新用的对象.你将它存储在堆栈中的缓冲区并不重要,缓冲区没有责任调用dtor,你这样做.
同样,允许优化器假设您不会尝试复制像这样的非平凡对象.优化器可能假设tmsg不包含有效Str对象,因为Str从不在那里调用对象构造函数.
您可以将此代码更改为
void f(Msg &tmsg)
{
new (&tmsg) Str ("hello");
}
Run Code Online (Sandbox Code Playgroud)
并修复了对齐问题,然后我认为它有明确定义的行为,至少我没有看到其他问题(除了泄漏).
可以在存储缓冲区中分配对象,但必须非常小心.我建议你听听好旧的ISO C++ FAQ的建议:
https://isocpp.org/wiki/faq/dtors#placement-new
建议:除非必须,否则请勿使用此"放置新"语法.仅在您真正关心对象放置在内存中的特定位置时才使用它.
...(如果您不知道"对齐"的含义,请不要使用放置新语法).你被警告了.
编辑:基于以上评论:
真正的代码试图将或多或少的任意类型打包到事件队列中.此队列的使用者恢复该类型并在完成后进行清理.
我建议你做的是使用a variant,like boost::variant或std::variant.这是一个类型安全的联合,它将管理缓冲区中新位置的详细信息,安全地复制和移动东西,调用dtors等.你可以拥有一个std::vector<variant<....>>或类似的队列,然后你就不会有这种类型的低 -水平问题.
另一种理解问题的方法是:如果f这样改变,并且修正了对齐问题,你可以这样做:
void f(Msg &tmsg)
{
Msg m;
new (&m) Str ("hello");
new (&tmsg) Str(*reinterpret_cast<Str*>(&m));
}
Run Code Online (Sandbox Code Playgroud)
因为您使用放置新语法调用复制文件,所以新版本Str在缓冲区中正确地开始其生命周期,tmsg并且它会复制该文件m.
| 归档时间: |
|
| 查看次数: |
93 次 |
| 最近记录: |