C++ 放置新的工作原理是啥?

Posted

技术标签:

【中文标题】C++ 放置新的工作原理是啥?【英文标题】:How C++ placement new works?C++ 放置新的工作原理是什么? 【发布时间】:2016-05-07 08:19:08 【问题描述】:

这个问题是为了确认我对这个概念的理解是正确的,并就使用风格和可能的优化征求专家意见。

我正在尝试理解“新安置”,以下是我想出的程序...

 #include <iostream>
 #include <new>

 class A 
 int *_a;
 public:
 A(int v) std::cout<<"A c'tor clalled\n";_a= new int(v);
 ~A() std::cout<<"A d'tor clalled\n"; delete(_a);
 void testFunction() std::cout<<"I am a test function &_a = "<<_a<<" a = "<<*_a<<"\n";
;
int main()

    A *obj1 = new A(21);
    std::cout<<"Object allocated at "<<obj1<<std::endl;
    obj1->~A();
    std::cout<<"Object allocated at "<<obj1<<std::endl;
    obj1->testFunction();
    A *obj2 = new(obj1) A(22);
    obj1->testFunction();
    obj2->testFunction();
    delete(obj1);// Is it really needed now? Here it will delete both objects.. so this is not the right place.
    //obj1->testFunction();
    //obj2->testFunction();
    return 0;

当我运行这个程序时,我得到以下 o/p

A c'tor clalled
Object allocated at 0x7f83eb404c30
A d'tor clalled
Object allocated at 0x7f83eb404c30
I am a test function &_a = 0x7f83eb404c40 a = 21
A c'tor clalled
I am a test function &_a = 0x7f83eb404c40 a = 22
I am a test function &_a = 0x7f83eb404c40 a = 22
A d'tor clalled
I am a test function &_a = 0x7f83eb404c40 a = 0
I am a test function &_a = 0x7f83eb404c40 a = 0

我有以下问题...

这是展示新位置的正确示例吗? 成员 a 是动态分配的(没有放置新的)。那么为什么它为 obj1 和 obj2 获得相同的地址。这只是巧合吗? D'tor 在第 15 行打电话是个好习惯吗?

还请指出您看到的任何我可以改进的地方,或者只是不要尝试。也欢迎任何好的参考或阅读。

【问题讨论】:

明确地说,取消引用已被 deleteed 的指针会调用 UB。 关于行为的问题 - 是的,正如你所怀疑的那样。第 22 和 23 行通过调用未定义的行为。 第 9 行也有罪。你调用A的析构函数deletes_a,然后调用testFunction,它取消引用_a @vikrant Point 2 纯属巧合。 不要使用std::endl,除非你需要它的额外功能。 '\n' 开始新的一行。 【参考方案1】:

这可能是给CodeReview.SE 的东西,让我在回答你的问题之前先评论一下你的源代码。

A *obj1 = new A(21);
std::cout<<"Object allocated at "<<obj1<<std::endl;
obj1->~A();

您通常不会在使用placement-new 创建的 对象上调用析构函数。在您的情况下,您破坏旧的并使用placement-new 构建一个新的。即使这样可行,您也应该实现一些 reset 功能来重置您的对象,而不是破坏和构造一个新对象。

17    obj1->testFunction();

这是 UB。你已经破坏了这个对象,你不应该在它上面调用任何方法。

18    A *obj2 = new(obj1) A(22);
19    obj1->testFunction();
20    obj2->testFunction();

这是okayish,请注意obj1obj2 是完全相同的对象。

21    delete(obj1);// Is it really needed now? Here it will delete both objects.. so this is not the right place.

你的评论是错误的。您不是要删除两个对象,而是要删除一个,稍后再删除。

22    obj1->testFunction();
23    obj2->testFunction();

这又是 UB,不要在解构或删除的对象上调用方法。 对于您的问题:

成员 _a 是动态分配的(没有放置新的)。那么为什么它为 obj1 和 obj2 获得相同的地址。这只是巧合吗?

不要称它们为obj1obj2,因为这两个变量指向同一个对象,但是是的,这是巧合。在第一个对象被破坏并释放此内存后,第二个对象分配了刚刚释放的相同数量的内存,分配器决定为您提供完全相同的内存。

D'tor 在第 15 行打电话是个好习惯吗?

不,不是。您需要调用析构函数的示例很少,其中之一是您的对象是由placement-new 创建的。在您的示例中,这没有副作用,因为您在解构旧对象后在同一位置构造了一个新对象,并且新对象与旧对象的类型相同,否则这可能会以某种方式严重破坏。

现在更多关于删除后的评论。让我们看看new 和placement-new 究竟做了什么。

一个新的:

从操作系统为新对象分配内存 在新对象上调用构造函数,地址 (this) 设置为分配器获得的内存块。

删除则相反:

调用对象的析构函数 释放内存块

现在到placement-new:placement-new 只是跳过第一步(分配内存)并调用该new 对象的构造函数,并将this 设置为您传递的地址。因此placement-new 的相反 只是调用析构函数,因为不存在placement-delete。

这意味着对于您的代码,在您调用析构函数后,您的第一个对象死亡,但您从未归还内存,这就是您可以在该内存中构造一个新对象的原因。现在当您调用 delete 时,第一个对象不再存在,只有它使用的内存,但是相同的内存现在被第二个对象阻塞,因此当您调用 delete 时,您不会删除两个对象,您只删除第二个一个(你解构它,然后释放内存块)。

您可以在isocpp's faq阅读更多关于主题placement-new 以及何时调用析构函数的信息

【讨论】:

感谢您的精彩解释并指出我的代码中的问题。【参考方案2】:

非常非常简单:new 可以被认为是做两件事:

    分配内存。 在分配的内存中放置构造对象。

不能保证malloc 确实被实现使用,但通常是这样。你不能假设它是关于实现的,但为了理解它是一个好的假设。

因此,以下被认为是等价的:

auto obj1 = new std::string("1");
// ↑ can be thought of as equivalent to ↓ 
auto obj2 = (std::string*)malloc(sizeof(std::string));
new(obj2) std::string("2");

delete 也是如此:

delete obj1;
// ↑ can be thought of as equivalent to ↓ 
obj2->~string();
free(obj2);

当您看到 newdelete 时,您就可以轻松地推断出它们的真正含义:分配之后是构造函数调用,以及析构函数调用之后是释放。

当您使用展示位置new 时,您决定单独处理第一步。内存仍然必须以某种方式分配,您只需完全控制它是如何发生的以及内存来自哪里。

因此,您必须分别跟踪两件事:

    内存的生命周期。

    对象的生命周期。

下面的代码演示了它们是如何相互独立的:

#include <cstdlib>
#include <string>
#include <new>

using std::string;

int main() 
    auto obj = (string*)malloc(sizeof(string));  // memory is allocated
    new(obj) string("1");  // string("1") is constructed
    obj->~string ();       // string("1") is destructed
    new(obj) string("2");  // string("2") is constructed
    obj->~string ();       // string("2") is destructed
    free(obj);             // memory is deallocated

如果对象的生命周期超过内存的生命周期,则您的程序具有 UB。确保内存总是超过对象的寿命。例如,这有 UB:

void ub() 
    alignas(string) char buf[sizeof(string)]; // memory is allocated
    new(buf) string("1");                     // string("1") is constructed
 // memory is deallocated but string("1") outlives the memory!

但这没关系:

void ub() 
    alignas(string) char buf[sizeof(string)]; // memory is allocated
    new(buf) string("1");                     // string("1") is constructed
    buf->~string();                           // string("1") is destructed
                                             // memory is deallocated

注意您需要如何使用alignas 正确对齐自动缓冲区。任意类型缺少alignas 会导致UB。它可能看起来有效,但这只是为了误导您。

在某些特定类型中,不调用析构函数和不正确对齐内存不会导致 UB,但您永远不应该对类型假设这样的事情。调用你的析构函数并进行对齐,如果它被证明是不必要的,它不会花费你任何成本 - 不会为这种类型生成额外的代码。

struct S 
  char str[10];

【讨论】:

更好地使用 ::operator new(sizeof(std::string)) 而不是 malloc(sizeof(std::string)) 那里实际等效。 Dito ::operator delete(pointer) 而不是 free(pointer) 放置new有什么意义,new(obj2) std::string("2");*obj2 = std::string("2");有何不同?【参考方案3】:

C++ 放置新的工作原理是什么?

...

我正在尝试理解“新安置”,以下是我想出的程序...

这两个答案都很好。但你也想知道它是如何工作的,所以,我将添加来自程序集的解释:


A *obj1 = new A(21);:
call operator new(unsigned long)
mov esi, 21
mov rdi, rax
mov rbx, rax
call A::A(int)

A *obj2 = new(obj1) A(22);
  mov esi, 22
  mov rdi, rbx
  call A::A(int)

就是这样,够清楚,不需要解释了吧?

【讨论】:

以上是关于C++ 放置新的工作原理是啥?的主要内容,如果未能解决你的问题,请参考以下文章

4G模块,wifi模块是啥,工作原理是啥

Sqoop工作原理是啥?

JDBC的工作原理是啥?

HTTP协议工作原理是啥 HTTP协议工作原理介绍【详解】

JAVA三框架工作原理是啥?

交流继电器的工作原理是啥?