这两种对象实例化方法有什么区别?

6
假设我有一个名为A的类:
Class A
{
...
}

以下两种方法实例化一个对象有什么区别呢?
void main(void)
{
    A a;  // 1
    A *pa=new A();  // 2
}

根据我的理解(还不确定):

  • 方法1将对象a分配到main()方法的堆栈帧中,因此该对象不能被删除,因为这种删除没有意义(还不知道原因,有人能解释一下吗?)。

  • 方法2将对象a分配到进程的堆中,并在main()方法的堆栈帧中也分配了一个A*变量pa,因此可以删除对象,并在删除后将pa赋值为null。

我理解正确吗?如果我的理解正确,有人能告诉我为什么不能从方法1的堆栈中删除对象a吗?

非常感谢...


4
请将 void main(void) 更改为 int main() - avakar
是的。通常情况下,你不应该写成function(void) {}这样的形式。许多权威人士认为这是一种糟糕的语法: http://www.parashift.com/c++-faq-lite/newbie.html#faq-29.4 - Merlyn Morgan-Graham
7个回答

6

对象 a 具有自动存储期,因此它将在定义它的作用域结束时自动删除。尝试手动删除它是没有意义的。手动删除仅适用于具有动态存储期的对象,例如使用new分配的*pa


4
  1. 对象的生命周期仅限于变量定义的范围内,一旦离开该范围,对象将被清除。在C++中,作用域由任何在 { 和相应的 } 之间的块定义。
  2. 这里只有指针在堆栈上,而不是对象,因此当您离开作用域时,只有指针将被清除,对象仍将存在于某个地方。

关于删除对象的部分,delete 不仅调用对象的析构函数,还释放其内存。这在堆栈的内存管理是由编译器自动完成的,而堆不是自动化的,需要调用 new 和 delete 来管理对象的生命周期。
通过调用 new 创建的任何对象必须被删除一次,忘记这样做会导致内存泄漏,因为对象的内存永远不会被释放。


1

方法1声明了一个变量并创建了一个对象。在方法2中,您创建了一个实例和指向它的指针。

编辑:在方法1中,对象将超出范围并将被自动删除。在方法2中,指针将被自动删除,但不是它所指向的内容。这将是您的工作。


错误,不,会调用默认构造函数。 - gregseth
1
嗨,fastcodejava,感谢您的回复。这是C++语法,不是C#。方法1确实创建了一个对象实例。因为我可以访问它的成员变量。 - smwikipedia
@smwikipedia - 我同意,我打字太快了。已经更正了答案。 - fastcodejava

1

分配内存有两个作用:
1)为对象分配内存
2)在分配的内存上调用构造函数

删除操作有两个作用:
1)在对象上调用析构函数
2)释放被销毁对象所使用的内存

当你在堆栈上分配(A a;),你告诉编译器“请为我创建一个对象,通过分配内存来调用构造函数。同时,当它超出范围时,请处理调用析构函数和释放内存的操作。谢谢!”一旦函数(main)结束,对象超出范围,析构函数被调用,内存被释放。

当你在堆上分配内存( A * pa = new A(); )时,你告诉编译器“请为我创建一个对象。我知道自己在干什么,不要麻烦调用析构函数或释放内存。我会在其他时间告诉您何时执行这些操作”。一旦函数(main)结束,你分配的对象保持在作用域内,不会被析构或释放。希望你在程序其他地方有指向它的指针(即将pa复制到具有更大作用域的其他变量中)。你需要在未来某个时刻告诉编译器析构对象并释放内存,否则会出现内存泄漏。

简而言之,“delete”命令仅适用于在堆上分配的对象,因为在C++中,堆是手动内存管理接口的一部分 - new/delete。它是堆分配器的命令,并且堆分配器不知道栈分配的对象。如果尝试对堆栈分配的对象调用delete,则与在随机内存地址上调用它相同 - 就堆分配器而言它们是相同的。非常类似于尝试在数组边界之外访问对象:

int a[10];
std::cout << a[37] << "\n"; // a[37] points at... ? no one knows!

它只是没有意图去做那个 :)

编辑: 附注:当您在main之外的函数中分配内存时,内存泄漏更加重要。当程序结束时,泄漏的内存被释放,因此主函数中的内存泄漏可能并不重要,具体取决于您的情况。然而,对于泄漏的对象,析构函数永远不会被调用。如果析构函数执行一些重要操作,比如关闭数据库或文件,则您可能会遇到更严重的错误。


从技术上讲,在构造函数完成之前,对象并不存在。构造函数将原始内存转换为对象。如果构造函数抛出异常,则根本没有对象存在。 - fredoverflow

1

想象一下栈结构为void* stack = malloc(1.000.000);
现在,这个内存块是由编译器和CPU在内部管理的。
它是一个共享内存区域,每个函数都可以使用它来存储临时对象。

这就是所谓的自动存储。您不能删除该内存的任何部分,因为它的目的是重复使用。
如果您显式地删除内存,该内存会返回到系统中,而您不希望在共享内存中发生这种情况。

从某种角度来看,自动对象也会被删除。当对象超出其范围时,编译器会放置
一个隐藏调用对象析构函数的指令,使内存再次可用。


谢谢Nick。因此,堆栈在编译时“计划”,并通过其生命周期由程序占用,而堆可以在运行时请求和释放。如果是这样,如果堆栈大小不够大怎么办?它的大小是否可以在运行时调整? - smwikipedia
@smwikipedia,确实,而且代码在线程中运行。通常每个线程都有自己的堆栈。如果您显式创建一个线程,则可以设置其堆栈大小。在Windows上,例如,请参见http://msdn.microsoft.com/en-us/library/ms682453%28VS.85%29.aspx。 - Nick Dandoulakis
同时编译器也有设置应用程序默认堆栈大小的选项。由于堆栈具有固定大小,可能会发生堆栈溢出:http://en.wikipedia.org/wiki/Stack_overflow - Nick Dandoulakis

1

你不能删除堆栈上的对象,因为它们是按照堆栈的方式在内存中实现的。当你在堆栈上创建对象时,它们会被添加到彼此之上。当对象离开作用域时,它们会按照相反的顺序从顶部销毁,也就是按照它们创建的相反顺序(从堆栈顶部添加,从堆栈顶部移除)。试图在堆栈上调用delete会打破这个顺序。这个类比就像试图从一堆纸张的中间拉出一些纸张。

编译器控制着如何在堆栈上创建和删除对象。它可以做到这一点,因为它知道每个堆栈上的对象的大小。由于堆栈大小在编译时设置,这意味着为堆栈上的东西分配内存非常快,比从受操作系统控制的堆中分配内存要快得多。


0

堆栈内存的管理方式与堆内存不同。 没有必要从堆栈中删除对象:它们将在作用域/函数结束时自动删除。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接