Google Sparsehash对不可复制的类型使用realloc()

时间:2018-09-21 03:41:38

标签: c++ undefined-behavior realloc gcc8 sparsehash

考虑以下简单程序:

List<WebElement> rows = new List<WebElement>(table.FindElements(By.TagName("tr")));
var rowsCount = rows.Count;
for(int i=0; i<rowsCount; i++)
{
    var row = rows[i];
    // do what you need to do on this row e.g. row.Click();
    row.Click();

    // click back and wait 
    back.Click();
    System.Threading.Thread.Sleep(10000);

    rows = new List<WebElement>(table.FindElements(By.TagName("tr")));
}

使用GCC 8.2和#include <string> #include <sparsehash/dense_hash_map> int main() { google::dense_hash_map<std::string, int> map; map["foo"] = 0; } (或-Wclass-memaccess)进行编译会产生警告:

-Wall

问题是:

  1. 这是未定义的行为吗?
  2. 您能否建议可以应用于应用程序代码的修补程序或解决方法(而不是通过更改Sparsehash或避免使用它)?
  3. (奖励积分),您可以构造一个实际上由于该原因而异常运行的程序(使用std :: string或您自己的非平凡类型)吗?到目前为止,尽管std :: string必须是非常常用的键类型,但我还没有看到使用std :: string作为键类型的代码中的任何问题。

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

3 个答案:

答案 0 :(得分: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;

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

3。 (加分)您是否可以构建一个因此而实际上行为异常的程序(使用std :: string或您自己的非平凡类型)?到目前为止,尽管std :: string必须是非常常用的密钥类型,但我还没有看到使用std :: string作为密钥类型的代码中的任何问题。

不容易。因为您正在尝试引起不确定的行为。在您的测试程序中,我将输入至少32个字符的字符串,这样就不会启动小型字符串优化。并且可以在gcc的堆中进行测试,以查看其是否损坏。看到 1

答案 1 :(得分:1)

  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 :(得分:0)

我想这段代码预期的是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不应仅与此类自记忆引用类型一起使用。