代码之家  ›  专栏  ›  技术社区  ›  John Zwinck

Google Sparsehash在类型上使用realloc(),这种类型不容易复制

  •  5
  • John Zwinck  · 技术社区  · 6 年前

    考虑一下这个简单的程序:

    #include <string>
    #include <sparsehash/dense_hash_map>
    
    int main()
    {
        google::dense_hash_map<std::string, int> map;
        map["foo"] = 0;
    }
    

    -Wclass-memaccess (或 -Wall )产生警告:

    sparsehash/internal/libc_allocator_with_realloc.h:68:40: warning:
    ‘void* realloc(void*, size_t)’ moving an object of non-trivially copyable type
        ‘struct std::pair<const std::__cxx11::basic_string<char>, int>’;
    use ‘new’ and ‘delete’ instead [-Wclass-memaccess]
        return static_cast<pointer>(realloc(p, n * sizeof(value_type)));
                                    ~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~
    

    问题是:

    1. 是未定义的行为吗?

    我在这里提出了一个问题: https://github.com/sparsehash/sparsehash/issues/149

    3 回复  |  直到 6 年前
        1
  •  2
  •   Deduplicator    6 年前
    1. 是的,这是未定义的行为。
      但别绝望,警长 std::string 不会在实现中存储任何内部指针,也不会在任何地方注册它们,无论如何它都会“工作”;按位复制相当于在目的地移动构造并破坏源。
      大多数人都是这样( 所有)字符串实现,SSO与否。

    2. 如果您可能会使用一个不保证具有轻微破坏性移动的类型,请使用不同的分配器(最后一个模板参数)来避免按位移动。

    3. 由于按位复制的无效移动而导致程序崩溃是很简单的。
      将此类型与 google::dense_hash_map

      class bang {
          bang* p;
      public:
          bang() : p(this) {}
          bang(bang const&) : bang() {}
          bang& operator=(bang const&) { return *this; }
          ~bang() { if (p != this) std::abort(); }
      };
      
        2
  •  2
  •   SJHowe    6 年前

    1是未定义的行为吗? 对。永远不要使用realloc()复制对象,因为有时它们有指向资源的内部指针。当两个不同的对象运行它们的析构函数时,问题就出现了。现在一个双重解除分配发生在 相同的资源 ,完全没有。

    2您能否建议一个可以应用于应用程序代码的修复或解决方法(不是通过更改Sparsehash或避免使用它)?

    尝试

    #include <memory>
    

    换行

    google::dense_hash_map<std::string, int> map;
    

    google::dense_hash_map<std::string, int, std::hash<std::string>, std::equal_to<std::string>, std::allocator> map;
    

    现在,它不会使用谷歌的分配器 libc_allocator_with_realloc

    3. (加分)你能构造一个程序,它实际上会因为这个(使用std::string或你自己的非平凡类型)而出现错误吗?到目前为止,我还没有看到使用std::string作为键类型的代码中有任何问题,尽管std::string必须是非常常用的键类型。

    不容易。因为你想引起不确定的行为。在您的测试程序中,我将输入长度至少为32个字符的字符串,因此小字符串优化不会启动。还有一些测试可以在gcc的堆中进行,以查看它是否已经损坏。看到了吗 1

        3
  •  0
  •   Oliv    6 年前

    我想这段代码预期了c++20类属性 . 本质上,这是一个可以安全地更改内存位置的对象。用c++的说法,这是一个可以通过处理对象表示来安全复制的对象,只要复制的对象不再被访问,甚至不被销毁,程序就会保持预期的行为。

    例如,该代码可能不会被C++ 20标准指定为“未定义行为”:

    alignas(string) unsigned char buffer[sizeof(string)];
    auto p = new(buffer) string{"test"};
    alignas(string) unsigned char buffer2[sizeof(string)];
    memcpy(buffer,buffer2,sizeof(string));//create a new string object copy of *p;
    auto np = reinterpret_cast<string*>(buffer2);
    (*np)[0]="r";
    // the object at p shall not be accessed, not even destroyed.
    

    如果一个类型有一个引用其自身任何部分的非静态数据成员,则该类型不应具有普通的可重定位性:

    struct fail{
        int a;
        int b;
        int* selected;
        fail(bool is_a,int i){
           if (is_a){ a=i; selected=&a;}
           else { b=i; selected=&b;}
           }
         };
    

    dense_hash_map 不应与这些自记忆引用类型一起使用。

    推荐文章