7

考虑这个简单的程序:

#include <string>
#include <sparsehash/dense_hash_map>

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

使用 GCC 8.2 和-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. 它是未定义的行为吗?
  2. 您能否建议可以应用于应用程序代码的修复或解决方法(不是通过更改 Sparsehash 或避免使用它)?
  3. (奖励积分)你能构造一个实际上因此而行为不端的程序(使用 std::string 或你自己的非平凡类型)吗?到目前为止,我在使用 std::string 作为键类型的代码中没有看到任何问题,尽管 std::string 必须是非常常用的键类型。

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

4

3 回答 3

2

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

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

于 2018-09-25T18:44:22.980 回答
2
  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(); }
    };
    
于 2018-09-25T20:03:51.410 回答
0

我想这段代码预计可能是 c++20 类属性trivially relocatable。本质上,这是一个可以安全更改内存位置的对象。用 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不应该与那些自记忆引用类型一起使用。

于 2018-09-27T17:51:26.333 回答