对 C++ 动态内存分配的确切含义有点困惑
我听说过关于动态的确切含义的相互矛盾的事情,以及自动的内存分配.我听说堆栈被称为自动内存分配和动态内存分配.我可以看到两者,因为堆栈内存块大小是在程序执行之前确定的,因此它的最大大小在运行时不会增长.然而,在程序执行期间,随着函数数据被压入和弹出堆栈,堆栈会不断地增长和缩小.
I've heard conflicting things concerning the exact meaning of dynamic, and for that matter automatic, memory allocation. I've heard the stack be referred to as both automatic memory allocation and as dynamic memory allocation. I can see both as the stack memory block size is determined before program execution and so it's maximum size cannot grow during runtime. However, during program execution the stack is constantly growing and shrinking as function data is pushed and popped on and off the stack.
那么从这个意义上说,这不是动态内存分配吗?
So in that sense isn't this dynamic memory allocation?
如果是,那么仅将堆称为动态的不是令人困惑吗?
If it is then isn't it confusing to only refer to the heap as being dynamic?
有人可以帮我澄清一下吗?
Can someone help me clarify this?
在撰写本文时,我似乎混淆了某些我不知道的概念.堆栈和堆内存管理的低级概念与 C++ 中相同事物的高级概念之间存在差异.有关此问题的说明,请参阅下面我接受的答案.
It seems I was confusing certain concepts that I wasn't aware of at the time of this writing. There is a difference between low-level concpets of stack and heap memory management and high level concepts of the same things in C++. For clarifcation on this please refer to my accepted answer below.
推荐答案
我会尽量消除困惑.首先,学习将低级内存模型概念(堆栈、堆)与 c++ 级内存概念分开.在 C++ 的世界中,stack
和 heap
并不意味着任何与低级模型中的堆栈或堆很相似的东西.
I will try to clear the confusion as much as I can. First of all, learn to separate low-level memory model concepts (stack, heap) from c++-level memory concepts. In the world of C++, stack
and heap
do not mean anything remotely resembling stack or heap in low-level model.
首先,让我们谈谈低级内存模型.传统上,内存分为堆栈"和堆"内存,我将在下面介绍.
First, let's talk about low-level memory model. Traditionally, memory is split between 'stack' and 'heap' memory, which I will cover next.
堆栈由所谓的堆栈指针"CPU 寄存器管理 - 它始终指示堆栈的顶部,并从高级内存地址不断地转到低级内存地址.由于寄存器始终指向堆栈的顶部,因此不需要与堆栈相关的任何实际内存管理 - 当您需要更多内存时,您只需减少存储在指针中的值 - 现在这是您的内存,它是被认为是为您分配的.当您不再需要内存时,您会增加值 - 现在内存是空闲的".显然,这种方法的问题在于它是不可持续的――您不能在块内释放(或分配)内存.因此,如果您为 3 个对象 A、B、C 分配了内存并且您不再需要对象 B,那么您就没有必要说 B 占用的内存可以自由使用 - 单个堆栈指针根本没有能力这样做.
The stack is managed by so-called 'stack pointer' CPU register - which always indicate the top of the stack and goes continuously from high-level memory addresses to low-level memory addresses. Since the top of the stack is always pointed to by the register, there is no need for any real memory management associated with stack - when you need more memory, you just decrease the value stored in the pointer - this your memory now and it is considered to be allocated for you. When you no longer need the memory, you increase the value - and the memory is 'free' now. Obviously, the problem with that approach is that it is not sustainable - you can not free (or allocate) memory within the block. So if you allocated memory for 3 objects, A, B, C and you no longer need the object B, there is no need you can say that memory occupied by B is free to be used - single stack pointer simply does not have capabilities to do so.
这将堆栈内存的使用限制在近距离"、短期对象的情况下 - 当您知道不需要选择性地释放与此范围内分配的对象相关联的任何内存时,并且可以只需尽快释放所有这些.这使得堆栈内存成为函数内定义的变量的理想存储 - 当函数退出时,所有变量都被释放.更好的是编译器可以自动为您执行此操作 - 您不必明确告诉编译器何时释放每个变量的内存 - 一旦代码执行离开它的范围,它将自动释放.
That limits the usage of the stack memory to the cases of 'close-reach', short-lived objects - when you you know that you do not need to selectively free any memory associated with objects allocated within this scope, and can simply free all of them soon enough. This make stack memory an ideal storage for a variables defined within a function - all of them are freed together when the function exits. What's even better is that compiler can do this automatically for you - you do not have to explicitly tell the compiler when to free the memory for each variable - it is going to be freed automatically once the code execution left it's scope.
还值得注意的是,堆栈分配和释放速度非常快――它们只需要一个寄存器算术运算.
It is also worth noting that stack allocation and freeing are uberfast - they only require a single register arithmetic operation.
但是,正如我之前所说,堆栈有局限性.堆内存就是为了克服这些问题 - 将在下面进行描述.
However, as I said before, stack has limitations. Heap memory is here to overcome those - and will be described next.
与堆栈(仅由简单的寄存器管理)不同,堆内存由复杂的结构和逻辑支持.您可以从堆中请求内存,也可以将内存返回到堆中,并且可以为每个对象独立执行此操作.因此,回到我最初的示例,当您为对象 A、B 和 C(大小都相同)请求内存并且不再需要对象 B 时,您可以为 B 返回内存并仍然保留 A 和 C.如果您需要要创建另一个与之前大小相同的对象 D 并为其请求内存,堆可以为您提供从 B 返回的内存.虽然不能保证(堆算法非常复杂),但这是一个足够好的简化.
Unlike the stack (which is only managed by simple register) heap memory is supported by complex structures and logic. You can request memory from the heap, and you can return memory back to the heap, and you can do it independently for every object. So, going back to my original example, when you requested memory for objects A, B and C (all the same size), and no longer need object B, you can return memory for B and still retain A and C. If you need to create another object, D, of the same size as those before and ask for the memory for it, heap can give you memory you returned from B. While it is not guaranteed (heap algorithms are very complex) this is a good enough simplification.
与堆栈内存不同,管理堆内存有其成本,实际上相对较高(尤其是在多线程环境中).这就是为什么不应该使用堆内存的原因,如果可以的话,但这本身就是一个巨大的话题,我现在不打算详述.
Unlike stack memory, managing heap memory has it's costs, which are actually comparatively quite high (especially in multithreaded environment). That's why heap memory should not be used if one can help it, but this is a huge topic on it's own, which I am not going to dwell on now.
堆内存的一个非常重要的属性是它必须由用户显式管理.您需要在需要时请求内存,在不再需要时将其归还,并且永远不要使用已归还的内存.不遵守这些规则会导致你的程序泄漏内存――也就是说,在不归还内存的情况下消耗内存,这将导致程序最终耗尽内存――以防你不归还内存;或者导致程序行为不正确(如果您在请求之前或之后使用内存),因为您将访问不属于您的内存.
One very important property of the heap memory is that it has to be explicitly managed by the user. You need to request memory when you need it, give it back when you no longer need it, and never use the memory you've given back. Failure to observe those rules would either make your program leak memory - that is, consume memory without giving it back, which would cause the program to eventually run out of memory - in case you do not give memory back; or cause the program to behave incorrectly (if you use the memory before requesting or after giving back) as you will be accessing memory which is not yours.
无论好坏,C/C++ 都使程序员免受那些低级内存概念的影响.相反,该语言指定每个变量都存在于某种类型的存储中,并且它的生命周期由存储类型定义.有 3 种类型的存储,概述如下.
For better or worse, C/C++ shield the programmer from those low-level memory concepts. Instead, the language specifies that every variable lives in a certain type of storage, and it's lifetime is defined by the storage type. There are 3 types of storage, outlined below.
此存储由编译器自动"管理(因此得名),不需要程序员对其进行任何操作.自动变量的一个例子是在函数体内定义的:
This storage is managed by the compiler 'automatically' (hence the name) and does not require the programmer to do anything about it. An example of automatic variable is one defined inside a function body:
void foo() {
int a;
}
a
这里是自动的.您无需担心为它分配内存或在不再需要它时清理它,并且编译器向您保证当您进入函数 foo() 时它会在那里,而当您退出 foo() 时它将不再存在.虽然它可能在堆栈上分配,但绝对不能保证它 - 它也可能被放入寄存器中.寄存器比任何内存都快得多,所以编译器会尽可能地使用它们.
a
here is automatic. You do not need to worry about allocating memory for it or cleaning it when it is no longer needed, and compiler guarantee you that it will be there when you enter function foo(), and will no longer be there when you exit foo(). While it might be allocated on the stack, there is absolutely no guarantee about it - it might as well be put in the register. Registers are so much faster than any memory, so compilers will make use of them whenever they can.
变量在程序退出之前一直保存在静态存储中.同样,开发人员无需担心它们的生命周期或清理内存 - 内存将在程序退出后被清理,而不是之前.静态持续时间变量的示例是在任何函数(全局变量)之外定义的变量、函数的静态局部变量和类的静态成员.下面的代码中var1、var2、var3都是静态存储中的变量:
Variables put in static storage live until the program exits. Again, developer does not need to worry about their lifetime, or cleaning up the memory - the memory will be cleaned up after program exits, and not before. An example of static duration variable is a variable, defined outside of any function (global variable), static local variables of the function, and static members of the class. In the code below var1, var2 and var3 are all variables within static storage:
代码(带有一些内联注释):
Code (with some inline comments):
int var1;
void foo() {
static int var2;
}
class A {
static int var3;
}
动态存储
动态存储变量由开发人员控制.当您需要它们时,您请求内存(通常使用 C 中的 malloc
或 C++ 中的 new
),并且在不再需要时必须将其归还(使用 free
在 C 中,delete
在 C++ 中).作为开发人员,您应该全神贯注于如何分配、使用和删除它们,并确保序列永不中断.未能遵守顺序是所有重大程序错误成为新闻的一个主要原因:).幸运的是,C++ 为您提供了简化此任务的特殊功能和类,但如果您使用 C 进行开发,您就只能靠自己了.在下面的示例中,动态分配 var4 指向的内存.
Dynamic storage
Dynamic storage variables are controlled by developer. When you need them, you request the memory (usually with malloc
in C or new
in C++) and you must give it back when you no longer need it (with free
in C, delete
in C++). As a developer, you should be paying all attention in how you allocate, use and delete those, and make sure the sequence is never broken. Failure to observe the sequence is a single major cause of all the great program bugs making the news :). Luckily, C++ has special features and classes for you that simplify this task, but if you develop in C, you are on your own. In the example below, memory to where var4 points is dynamically allocated.
代码:
void foo() {
int* var4;
// Here is the major source of confusion. var4 itself is **automatic**
// you do not need to allocate or free var4 memory, so you can use it
// like this:
var4 = NULL; // Not an error!!!
// However, you can't use the memory var4 points to yet!
// Following line would cause incorrect behavior of the program:
// *var4 = 42; // NEVER EVER!!!
// Instead, you need to allocate the memory first (let's assume, we are in C++
var4 = new int();
// Now the memory was allocated, we can use it
*var4 = 42; // Correct!
// we no longer need this memory, so let's free it:
delete var4;
// This did not change var4 itself (unless there is a special case)
// so technically, it still points to the memory which was former
// belonging to you. But the memory is no longer yours!!!
// you can't read or write it!
// Following code is bad-bad-bad:
// int x = *var4; // NEVER EVER!
}
如您所见,使用动态内存伴随着最谨慎和警告标志.这就是为什么在 C++ 中有一些特殊的工具可以让这变得更容易,并且没有人会编写我上面写的代码.但是,我的帖子已经方式太长了,所以 C++ 中的适当内存管理将留待其他场合 :)
As you've seen, using dynamic memory comes with most caution and warning signs. This is why in C++ there are special facilities to make this easier, and no one is expected to write the code I have wrote above. However, my post is already way to long, so proper memory management in C++ will be left for another occasion :)
相关文章