我的C ++代码段

时间:2017-10-01 01:08:54

标签: 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;                                                                                                                                                                                                    
}                                                                                                                                                                                                               

当我尝试运行时,我得到:

$ g++ main.cpp -g -std=c++11 && ./a.out
a.out: main.cpp:24: int main(int, char**): Assertion `ptr->v == "hello"' failed.
Aborted

有什么想法吗?我现在已经盯着这几个小时了,我一直无法弄明白。

1 个答案:

答案 0 :(得分:5)

根据C ++标准,此代码不合法​​。有很多问题:

  1. 对齐。您无法确保Str的存储空间与std::string的边界对齐,因此您的代码具有未定义的行为,无需诊断。使用std::aligned_storage_t比使用std::array更简单。

  2. 您正试图通过复制基础字节来复制std::string。这不合法,标准不会授予您许可。它违反了C ++中非平凡类类型的基本生命周期要求,并且在这种情况下违反了严格的别名规则。

  3. 在这个功能中,坏事正在发生

    void f(Msg &tmsg)                                                                                                                                                                                               
    {                                                                                                                                                                                                               
       Msg m;                                                                                                                                                                                                       
       new (&m) Str ("hello");                                                                                                                                                                                      
       tmsg = m;                                                                                                                                                                                                    
    }                                                                                                                                                                                                               
    
    发生tmsg = m

    这是底层字节获取副本的时候,但这不是你可以安全地复制对象的方式。如果它非常重要,比如std :: string,并拥有像堆分配缓冲区这样的资源,则需要调用复制构造函数,否则类不能强制执行其保证。 (该行本身不会导致未定义的行为,但是当您尝试将tmsg字节重新解释为有效的Str时,即UB。)

    另请注意,因为您使用了展示位置new,并且您从未在任何地方调用过dtor,所以您正在泄漏您新推出的对象。你把它存储在堆栈中的缓冲区并不重要,缓冲区没有责任调用你的dtor。

    同样,允许优化器假设您不会尝试复制像这样的非平凡对象。优化器可能会认为tmsg不包含有效的Str对象,因为永远不会在那里调用Str对象构造函数。

    您可以将此代码更改为

    void f(Msg &tmsg)                                                                                                                                                                                               
    {                                                                                                                                                                                                               
       new (&tmsg) Str ("hello");                                                                                                                                                                                      
    }                                                                                                                                                                                                               
    

    并修复了对齐问题,然后我认为它有明确定义的行为,至少我没有看到其他问题(泄漏除外)。

    可以在存储缓冲区中分配对象,但必须非常小心。我建议你听听好旧的ISO C ++ FAQ的建议:

    https://isocpp.org/wiki/faq/dtors#placement-new

      

    建议:除非必须,否则请勿使用此“放置新”语法。仅在您真正关心对象放置在内存中的特定位置时才使用它。

         

    ...    (如果您不知道“对齐”的含义,请不要使用放置新语法)。你被警告了。

    编辑:根据上述评论:

      

    真正的代码试图将或多或少的任意类型打包到事件队列中。此队列的使用者恢复该类型并在完成后进行清理。

    我建议您使用variant,例如boost::variantstd::variant。这是一个类型安全的联合,它将管理缓冲区中新放置的详细信息,安全地复制和移动东西,调用dtors等。你可以有一个std::vector<variant<....>>或类似的队列,然后你就不会有这种类型的低级别问题。

    了解问题的另一种方法是:如果f如此更改,并且修正了对齐问题,则可以执行以下操作:

    void f(Msg &tmsg)                                                                                                                                                                                               
    {                                                                                                                                                                                                               
       Msg m;                                                                                                                                                                                                       
       new (&m) Str ("hello");                                                                                                                                                                                      
       new (&tmsg) Str(*reinterpret_cast<Str*>(&m));
    }                                                                                                                                                                                                       
    

    由于您使用展示位置新语法调用副本ctor,因此新Str在缓冲区tmsg中正确开始其生命周期,并在m中复制该$23,455。 / p>