在堆栈上动态分配内存

Dynamically allocating memory on stack

本文关键字:内存 动态分配 堆栈      更新时间:2023-10-16

有这样的代码:

#include <iostream>
int main()
{
    int a;
    int* p = new (&a) int(2);
    std::cout << a << std::endl;
    // delete p; error BLOCK TYPE IS INVALID
    std::cin.get();
    return 0;
}

输出为:

2

为什么可以在堆栈上动态分配内存?(我认为堆是正确的地方做这个)。为什么在这种情况下,delete操作符返回错误,而new操作符可以工作?

这是使用位置新语法。放置new根本不分配内存,而是在特定位置构造对象的一种方法。在本例中,内存来自堆栈。不需要。删除出现问题,因为您没有添加内存。

有从堆栈动态分配内存的方法(alloca),但这不是这里发生的。

int* p = new (&a) int(2);

这叫做place -new。它不分配内存。它在a相同的内存中构造对象。在placement new中,由用户指定new操作符构造对象的内存区域。在上面的代码中,通过在new关键字后面编写(&a)表达式来指定内存区域。因为&a不是动态分配的内存,所以不能delete它:

delete p; //runtime-error

它会给出运行时错误,它试图删除变量a所在的内存。

然而,如果你动态分配内存,那么你可以删除它。让我们假设,A是某个类,那么你应该这样做:

char *buffer = new char[sizeof(A)]; //allocate memory of sizeof(A);
///ASSUMPTION: the buffer is properly align as required by the type A
//use placement-new to construct an object at the specified memory region
A *pA = new (buffer) A(/*..parameters..*/); 
//...
//incorrect way to delete the memory!
//delete pA; //incorrect
//before deleting the memory you should be calling the destructor explicitly as
pA->~A(); //call the destructor explicitly - must do it
//now deallocate the memory as
delete []buffer;

这是placementnew最简单的例子,它只解释了语法。但故事并没有到此结束;这是一个开始,为了使它正常工作,buffer指向的内存必须为对象类型正确对齐,在上面的例子中,我只是假设是这样。在实际代码中,您不能做出这样危险的假设。现在阅读这个FAQ:

    什么是"新布局",为什么我要使用它?

这就是placement new: http://www.parashift.com/c++-faq-lite/dtors.html#faq-11.10

你可以选择传递一个地址给new,它只会调用对象的构造函数(如果有的话)。因为没有分配内存,所以用delete释放内存是错误的。只需调用对象的析构函数(如果有的话),就完成了。

c++分离了内存分配对象生命周期的概念。与C相比,这可能是该语言最重要的"新"方面之一。在C中,没有这种区别,因为变量完全由它们的内存决定,而在c++中,对象有一个更抽象的"状态"概念,与底层内存不同。

让我们先看看内存:

{
    char buf[100];  // automatic allocation, scoped lifetime
}
{
    void * p = std::malloc(100);      // dynamic allocation, manually managed
    void * q = ::operator new(100);   // essentially identical
    // ...
    ::operator delete(q);             // manual deallocation
    std::free(p);                     // ditto
}
另一方面,对象的生命周期是一个单独的主题:
{
    Foo x;    // automatic storage, scoped lifetime.
              // Implies automatic memory allocation for sizeof(Foo) bytes.
}
{
    Foo * px = ::new Foo;      // dynamic storage, manual lifetime,
                               // implies dynamic allocation via ::operator new()
    Foo * py = ::new (q) Foo;  // dynamic storage and manual lifetime, uses memory at q
    // ...
    delete px;                 // destroy object _and_ deallocate memory
    py->~Foo();                // destroy object. Memory was never our business to start with.
}

正如你所看到的,内存和对象生命周期的分离增加了很多灵活性:我们可以让动态对象驻留在自动内存中,或者自己负责分配,并为重复的对象构造重用内存。标准的newdelete表达式结合了分配和构造,但这只是最常用操作的快捷方式。原则上,您可以完全自由地分别处理内存和对象生命周期。

这个想法支持分配器的概念,这是c++标准库中的核心概念。