Eri*_*ric 75 c++ heap stack pointers
假设我定义了一些类:
class Pixel {
public:
Pixel(){ x=0; y=0;};
int x;
int y;
}
Run Code Online (Sandbox Code Playgroud)
然后使用它编写一些代码.我为什么要这样做?
Pixel p;
p.x = 2;
p.y = 5;
Run Code Online (Sandbox Code Playgroud)
来自Java世界我总是写:
Pixel* p = new Pixel();
p->x = 2;
p->y = 5;
Run Code Online (Sandbox Code Playgroud)
他们基本上做同样的事情,对吗?一个在堆栈上而另一个在堆上,所以我将在以后删除它.两者之间有什么根本区别吗?为什么我更喜欢一个呢?
jal*_*alf 187
是的,一个在堆栈上,另一个在堆上.有两个重要的区别:
delete
自己来避免它们,而是将它包装在delete
内部调用的堆栈分配对象中,典型地在它们的析构函数中.如果您尝试手动跟踪所有分配,并delete
在正确的时间调用,我保证每100行代码至少会有一次内存泄漏.作为一个小例子,请考虑以下代码:
class Pixel {
public:
Pixel(){ x=0; y=0;};
int x;
int y;
};
void foo() {
Pixel* p = new Pixel();
p->x = 2;
p->y = 5;
bar();
delete p;
}
Run Code Online (Sandbox Code Playgroud)
相当无辜的代码,对吧?我们创建一个像素,然后我们调用一些不相关的函数,然后我们删除像素.有内存泄漏吗?
答案是"可能的".bar
抛出异常会发生什么?delete
永远不会被调用,像素永远不会被删除,我们会泄漏内存.现在考虑一下:
void foo() {
Pixel p;
p.x = 2;
p.y = 5;
bar();
}
Run Code Online (Sandbox Code Playgroud)
这不会泄漏内存.当然,在这个简单的情况下,所有东西都在堆栈中,因此它会自动清理,但即使Pixel
类在内部进行了动态分配,也不会泄漏.这个Pixel
类只会被赋予一个删除它的析构函数,无论我们如何离开foo
函数,都会调用这个析构函数.即使我们因为bar
抛出异常而离开它.以下,有点人为的例子说明了这一点:
class Pixel {
public:
Pixel(){ x=new int(0); y=new int(0);};
int* x;
int* y;
~Pixel() {
delete x;
delete y;
}
};
void foo() {
Pixel p;
*p.x = 2;
*p.y = 5;
bar();
}
Run Code Online (Sandbox Code Playgroud)
Pixel类现在在内部分配一些堆内存,但是它的析构函数负责清理它,因此在使用该类时,我们不必担心它.(我应该提一下,这里的最后一个例子很简单,为了显示一般原则.如果我们实际使用这个类,它也包含几个可能的错误.如果y的分配失败,x永远不会被释放,如果Pixel被复制,我们最终会尝试删除相同的数据.所以在这里用最后一个例子来看看.真实世界的代码有点棘手,但它显示了一般的想法)
当然,相同的技术可以扩展到除内存分配之外的其他资源.例如,它可用于保证文件或数据库连接在使用后关闭,或者释放线程代码的同步锁.
Mar*_*ork 30
在添加删除之前,它们不一样.
您的示例过于简单,但析构函数实际上可能包含执行某些实际工作的代码.这被称为RAII.
所以添加删除.确保即使异常传播也会发生.
Pixel* p = NULL; // Must do this. Otherwise new may throw and then
// you would be attempting to delete an invalid pointer.
try
{
p = new Pixel();
p->x = 2;
p->y = 5;
// Do Work
delete p;
}
catch(...)
{
delete p;
throw;
}
Run Code Online (Sandbox Code Playgroud)
如果你选择了一些更有趣的东西,比如一个文件(这是一个需要关闭的资源).然后在Java中使用指针正确执行此操作.
File file;
try
{
file = new File("Plop");
// Do work with file.
}
finally
{
try
{
file.close(); // Make sure the file handle is closed.
// Oherwise the resource will be leaked until
// eventual Garbage collection.
}
catch(Exception e) {};// Need the extra try catch to catch and discard
// Irrelevant exceptions.
// Note it is bad practice to allow exceptions to escape a finally block.
// If they do and there is already an exception propagating you loose the
// the original exception, which probably has more relevant information
// about the problem.
}
Run Code Online (Sandbox Code Playgroud)
C++中的相同代码
std::fstream file("Plop");
// Do work with file.
// Destructor automatically closes file and discards irrelevant exceptions.
Run Code Online (Sandbox Code Playgroud)
虽然人们提到速度(因为在堆上查找/分配内存).就个人而言,这对我来说不是一个决定因素(分配器非常快,并且针对不断创建/销毁的小对象的C++使用进行了优化).
我的主要原因是物体生命时间.本地定义的对象具有非常特定且定义良好的生命周期,并且保证在最后调用析构函数(因此可以具有特定的副作用).另一方面,指针控制具有动态寿命的资源.
谁拥有指针的概念.所有者有责任在适当的时间删除对象.这就是为什么你很少在真实程序中看到像这样的原始指针(因为没有与原始指针相关的所有权信息).相反,指针通常包含在智能指针中.智能指针定义谁拥有内存的语义,从而定义谁负责清理它.
例如:
std::auto_ptr<Pixel> p(new Pixel);
// An auto_ptr has move semantics.
// When you pass an auto_ptr to a method you are saying here take this. You own it.
// Delete it when you are finished. If the receiver takes ownership it usually saves
// it in another auto_ptr and the destructor does the actual dirty work of the delete.
// If the receiver does not take ownership it is usually deleted.
std::tr1::shared_ptr<Pixel> p(new Pixel); // aka boost::shared_ptr
// A shared ptr has shared ownership.
// This means it can have multiple owners each using the object simultaneously.
// As each owner finished with it the shared_ptr decrements the ref count and
// when it reaches zero the objects is destroyed.
boost::scoped_ptr<Pixel> p(new Pixel);
// Makes it act like a normal stack variable.
// Ownership is not transferable.
Run Code Online (Sandbox Code Playgroud)
还有其他人.
Cly*_*yde 25
从逻辑上讲,他们做同样的事情 - 除了清理.只是您编写的示例代码在指针大小写中存在内存泄漏,因为该内存未被释放.
来自Java背景,你可能还没有完全准备好C++围绕着分配的内容以及负责释放它的人.
通过在适当的时候使用堆栈变量,您不必担心释放该变量,它会随着堆栈帧而消失.
显然,如果你非常小心,你总是可以在堆上分配并手动免费,但是好的软件工程的一部分是以不会破坏的方式构建事物,而不是信任你的超级人类程序员 - 我永远不会犯错.
Tim*_*Tim 14
"为什么不在C++中使用指针"
一个简单的答案 - 因为它成为管理内存的一个巨大问题 - 分配和删除/释放.
自动/堆栈对象删除了一些繁忙的工作.
这是我对这个问题首先要说的.
Ski*_*izz 11
代码:
Pixel p;
p.x = 2;
p.y = 5;
Run Code Online (Sandbox Code Playgroud)
没有动态分配内存 - 没有搜索空闲内存,没有更新内存使用,没有.它完全免费.编译器在编译时为变量保留堆栈空间 - 它有足够的空间来保留并创建一个操作码来移动堆栈指针所需的数量.
使用new需要所有内存管理开销.
那么问题就变成了 - 你想为你的数据使用堆栈空间或堆空间吗?像'p'这样的堆栈(或局部)变量不需要解除引用,而使用new会增加一个间接层.
eee*_*aii 10
是的,起初有意义,来自Java或C#背景.要记住释放你分配的内存似乎没什么大不了的.但是当你第一次发现内存泄漏时,你会感到头疼,因为你很快就释放了所有东西.然后第二次发生,第三次你会更加沮丧.最后,由于内存问题导致六个月的头痛问题,你将开始厌倦它并且堆栈分配的内存将开始变得越来越有吸引力.多么美好和干净 - 只需将它放在堆栈上就可以忘掉它.很快你就可以随时使用它了.
但是 - 这种体验无可替代.我的建议?现在就试试吧.你会看到的.
没有新东西的最好理由是,当事物在堆栈中时,你可以进行非常确定的清理.在Pixel的情况下,这不是那么明显,但在说文件的情况下,这变得有利:
{ // block of code that uses file
File aFile("file.txt");
...
} // File destructor fires when file goes out of scope, closing the file
aFile // can't access outside of scope (compiler error)
Run Code Online (Sandbox Code Playgroud)
在新建文件的情况下,您必须记住删除它以获得相同的行为.在上面的例子中似乎是一个简单的问题.但是,请考虑更复杂的代码,例如将指针存储到数据结构中.如果将该数据结构传递给另一段代码怎么办?谁负责清理工作.谁会关闭你的所有文件?
当你没有新的东西时,当变量超出范围时,析构函数就会清理资源.因此,您可以更加自信地成功清理资源.
这个概念被称为RAII - 资源分配是初始化,它可以大大提高您处理资源获取和处置的能力.
第一种情况并不总是堆栈分配.如果它是对象的一部分,它将被分配到对象所在的任何位置.例如:
class Rectangle {
Pixel top_left;
Pixel bottom_right;
}
Rectangle r1; // Pixel is allocated on the stack
Rectangle *r2 = new Rectangle(); // Pixel is allocated on the heap
Run Code Online (Sandbox Code Playgroud)
堆栈变量的主要优点是:
创建对象后,堆上分配的对象与堆栈上(或任何位置)分配的对象之间没有性能差异.
但是,除非您使用指针,否则不能使用任何类型的多态性 - 该对象具有完全静态类型,该类型在编译时确定.
归档时间: |
|
查看次数: |
12688 次 |
最近记录: |