C++ 在allocator<string> 中销毁元素时会导致double free 吗?

C++ does it lead to double free when destroy element in allocator<string>?

C++ 分配器。我知道 String 将在内部实现中分配一个带有 new 的块缓冲区,并在析构函数中释放它(调用 delete[])。

我的问题是使用时是否会免费双倍allocator<string>

  1. 首先在字符串析构函数中释放。
  2. 第二次解除分配指向字符串的缓冲区已释放。

另外,string的缓冲区地址和allocate(n)是否有相同的区域?

#include <iostream>
#include <memory>
#include <string>

using namespace std;

int main(int argc, char **argv)
{
    const int cnt = 10;
    allocator<string> alloc;
    auto p = alloc.allocate(cnt);

    alloc.construct(p);
    for (int i = 0; i < cnt; ++i)
    {
        cout << p+i << endl; // print buffer address
    }

    alloc.destroy(p); // will it free buffer of string?

    alloc.deallocate(p, cnt); // will it free buffer of string again?

    return 0;
}

让我们从delete

开始

当您 delete 一个对象时,会发生两件事:

  1. 那个对象的析构函数被调用
  2. new 对象分配的内存正在释放回堆

placement newdelete

您可以使用放置 new 语法在现有内存缓冲区上构造对象:

const char* charString = "Hello, World";
// allocate the required memory
void *mem = ::operator new(sizeof(Buffer) + strlen(charString) + 1);
// construct a "Buffer" object on an existing memory block
Buffer* buf = new(mem) Buffer(strlen(charString));

// ...

// destruct the "Buffer" object without releasing the memory
buf->~Buffer();
// deallocate the memory
::operator delete(mem);

当然,对于此示例,您可以只使用普通的 newdelete,但它显示了如何将内存分配与其构建以及销毁与释放分开。

例如,如果您管理内存池并且当对象被销毁时内存可以回收回内存池而不是返回堆,则此技术很有用。

std::allocator

std::allocator 为您提供上述行为 - 使用方法 allocateconstructdestroydeallocate.

const int cnt = 10;
allocator<string> alloc;
auto p = alloc.allocate(cnt); // memory is allocated
alloc.construct(p);           // object is constructed on that memory

// ...

alloc.destroy(p);         // object is destructed
alloc.deallocate(p, cnt); // memory is deallocated

注意当string对象被析构时,std::string析构函数会调用delete内部分配,但是对象本身占用的内存在上面的代码中只在调用 alloc.deallocate.


constructdestroy 在 C++17 中被弃用

C++17 声明了 std::allocator 的方法 constructdestroy deprecated 而 C++20 使它们成为 已过时。因此,从 C++17 开始,您必须在使用分配器分配内存后使用 placement new,并在使用分配器释放内存后直接调用析构函数:

const int cnt = 10;
allocator<string> alloc;
auto p = alloc.allocate(cnt); // memory is allocated
// alloc.construct(p);        // deprecated in C++17, obsolete in C++20
new(p) string; // construct the object with placement new

// ...

// alloc.destroy(p);      // deprecated in C++17, obsolete in C++20
p->~string(); // destruct the object by calling the destructor
alloc.deallocate(p, cnt); // memory is deallocated

最后注意:还有一个叫做 placement delete 的东西,如果对象的构造函数抛出异常,它不会由程序员直接调用,而只能从 placement new 调用。所以不要对此感到困惑。在不释放对象本身占用的内存的情况下就地销毁对象的方法是直接调用其析构函数,如上所示(或者如果您在 C++17 之前使用 std::allocator,则通过调用分配器 destroy 方法) .

当你这样写的时候:

Foo *foo = new Foo();

发生了两件事:

  1. Foo 对象分配了一些堆 space。
  2. 调用Foo()构造函数,this指向新分配的space。

稍后,您删除 Foo 对象:

delete foo;

还有两件事发生了:

  1. 析构函数~Foo()被调用。
  2. 为 Foo 实例分配的内存被释放回堆。

std::allocator class 只允许您手动执行这四个步骤中的每一个。

如果你有一个 allocator<string> alloc 并且你调用 alloc.allocate 然后 alloc.construct,这与 new string() 是一样的。当您调用 alloc.destroy 然后调用 alloc.deallocate 时,这与删除 string 指针相同。

所以不,不会有任何额外的免费活动。对 destroy 的调用导致 string 释放它为其缓冲区分配的任何内存,然后对 deallocate 的调用释放用于 string 对象本身的内存.

我没有完全理解你关于该地区的问题。分配用于存储 string 实例的内存与 string 为其缓冲区分配的内存无关。

您可以继承std::string添加一些打印语句,以更好地理解(或者您需要调试并进入STL容器和功能)

#include <iostream>
#include <memory>
#include <string>

using namespace std;

class MyString : public string {
    public:
    MyString() = default;
    ~MyString() { cout << "destructor of MyString:" << this << endl; }
};
int main(int argc, char **argv)
{
    const int cnt = 10;
    allocator<MyString> alloc;
    auto p = alloc.allocate(cnt);

    alloc.construct(p);
    for (int i = 0; i < cnt; ++i)
    {
        cout << p+i << endl; // print buffer address
    }

    alloc.destroy(p); // will it free buffer of string?

    cout << "alloctor destrory called" << endl;

    alloc.deallocate(p, cnt); // will it free buffer of string again?

    return 0;
}

在这里执行:

https://coliru.stacked-crooked.com/a/13302c7f0c4db07b