当前位置: 首页 > 知识库问答 >
问题:

C放置新如何工作?

吉泰宁
2023-03-14

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

我正在努力理解“新安置”,下面是我提出的方案。。。

 #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的地址相同呢

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

共有3个答案

伍心水
2023-03-14

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)

这就是它的工作原理,足够清楚,不需要更多的解释,对吗?

赵征
2023-03-14

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

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

通常情况下,您永远不会对未使用placement new创建的对象调用析构函数。在你的例子中,你破坏了旧的,并用新的位置构建了一个新的。即使这样做可行,您也应该实现一些重置功能来重置对象,而不是破坏和构建一个新的对象。

17    obj1->testFunction();

这是UB。您已经销毁了对象,您不应该对其调用任何方法。

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

这很好,但请注意,obj1和obj2是完全相同的对象。

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的地址相同呢

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

D'tor在15号线上打电话是一种好做法吗?

不,不是。很少有例子说明你为什么需要调用析构函数,其中之一是你的对象是通过放置new创建的。在您的示例中,这没有副作用,因为在解构旧对象后,您在同一位置构造了一个新对象,并且新对象与旧对象的类型相同,否则这可能会在某种程度上严重破坏。

现在,请在删除后再谈谈您的评论。让我们看看一个新的和一个新的位置实际上做了什么。

一个新的DOS:

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

删除的作用正好相反:

  • 调用对象的析构函数
  • 解除分配内存块

现在来看放置新:放置新只是跳过第一步(分配内存)并调用新对象的构造函数,并将this设置为您传递的地址。因此,与放置新相反的只是调用析构函数,因为不存在放置删除。

这意味着你的代码,在你调用析构函数后,你的第一个对象死了,但你从来没有把内存还给你,这就是为什么你可以在内存中构造一个新对象。现在当你调用删除时,第一个对象不再存在,只有它使用的内存,但是同一个内存现在被第二个对象阻塞了,因此当你调用删除时,你不会删除两个对象,你只删除第二个对象(你解构它,然后释放内存块)。

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

盖昀
2023-03-14

这真的非常简单:新建可以被认为是做两件事:

  1. 分配内存

无法保证实现实际使用了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 obj1;
// ↑ can be thought of as equivalent to ↓ 
obj2->~string();
free(obj2);

然后,当您看到new和delete时,您可以很容易地对它们进行推理,因为它们实际上是什么:分配之后是构造函数调用,析构函数调用之后是释放。

当您使用placement 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];
    }
    

  •  类似资料:
    • 假设有一个结构,其构造函数没有初始化所有成员变量: 如果我将某个缓冲区设置为0,在该缓冲区上使用placement new创建一个Foo实例,然后从该实例读取x,这是定义的行为吗?

    • 这是一款放置类游戏,你的目标是建立一个强大的自动化工厂和生产链,从简单的东西一直升级到生产火箭。  

    • 问题内容: 我针对其运行的网站位于内部服务器上,因此无法提供链接,但是我可以发布一些单击“显示元素”时显示的相关代码。 有五个与此相关的元素: 组1 学生 移动1 组2 移动2 此页面显示学生组,并允许用户在组之间拖动学生。每个组都有一个移动元素。棘手的部分是,仅当用户将学生元素拖到该组上时,任何给定组的“移动”按钮才会显示,而该学生并非来自该组。 目的是将学生转移到新的小组中,然后再移回原来的小

    • 问题内容: 我有一个设置100%宽度的主包装div。在里面,我想有两个div,一个是固定宽度的,另一个是填充其余空间的。我如何浮动第二个div来填充其余的空间。谢谢你的帮助。 问题答案: 有很多方法可以满足您的要求: 使用CSS 属性:

    • 问题内容: IE浏览器忽略缩放设置不起作用,我的代码如下,为什么它不起作用?我收到了错误消息(selenium.common.exceptions.SessionNotCreatedException:消息:启动Internet Explorer时发生意外错误。浏览器缩放级别设置为125%。应将其设置为100%) 问题答案: 不 ,在使用 InternetExplorerDriver时, 您不应忽