堆栈是干什么用的?
每个程序都由函数/子例程/无论您选择的语言如何称呼它们组成。几乎总是,这些函数都有一些本地状态。即使在一个简单的 for 循环中,您也需要在某个地方跟踪循环计数器,对吗?这必须存储在内存中的某个地方。
关于函数的事情是它们几乎总是做的另一件事是调用其他函数。那些其他函数有它们自己的局部状态——它们的局部变量。您不希望本地变量干扰调用者中的本地变量。必须发生的另一件事是,当 FunctionA 调用 FunctionB 然后必须执行其他操作时,您希望 FunctionA 中的局部变量仍然存在,并且在 FunctionB 完成时具有相同的值。
跟踪这些局部变量是堆栈的用途。每个函数调用都是通过设置所谓的堆栈帧来完成的。堆栈帧通常包括调用者的返回地址(用于函数完成时)、任何方法参数的值以及任何局部变量的存储。
当调用第二个函数时,会创建一个新的堆栈帧,将其压入堆栈顶部,然后调用就会发生。新函数可以愉快地处理它的堆栈帧。当第二个函数返回时,它的堆栈框架被弹出(从堆栈中移除)并且调用者的框架像以前一样回到原位。
这就是堆栈。那么堆是什么?它有类似的用途——存储数据的地方。但是,通常需要比单个堆栈帧寿命更长的数据。它不能进入堆栈,因为当函数调用返回时,它的堆栈框架被清理并繁荣 - 你的数据在那里。所以你把它放在堆上。堆是一个基本上非结构化的内存块。你要求 x 个字节,你得到它,然后就可以参与其中。在 C / C++ 中,堆内存保持分配状态,直到您显式取消分配。在垃圾回收语言(Java/C#/Python/等)中,堆内存中的对象不再使用时将被释放。
从上面解决您的具体问题:
栈溢出和缓冲区溢出有什么区别?
它们都是超出内存限制的情况。堆栈溢出是特定于堆栈的;您已经编写了代码(递归是一个常见的原因,但不是唯一的原因),因此它有太多的嵌套函数调用,或者您在堆栈上存储了很多大的东西,并且空间不足。大多数操作系统都对堆栈可以达到的最大大小进行了限制,当您达到该限制时,您会遇到堆栈溢出。现代硬件可以检测到堆栈溢出,这通常对您的进程来说是厄运。
缓冲区溢出有点不同。所以第一个问题 - 什么是缓冲区?嗯,这是一块有限的内存。该内存可能在堆上,也可能在堆栈上。但重要的是你知道你可以访问 X 个字节。然后,您编写一些代码,将 X + 更多字节写入该空间。编译器可能已经将缓冲区之外的空间用于其他内容,并且由于写入过多,您已经覆盖了其他内容。缓冲区溢出通常不会立即看到,因为直到您尝试对已被丢弃的其他内存执行某些操作时才会注意到它们。
另外,还记得我提到过返回地址也存储在堆栈中吗?这是由于缓冲区溢出导致的许多安全问题的根源。您的代码使用堆栈上的缓冲区并且存在溢出漏洞。聪明的黑客可以构建溢出缓冲区的数据以覆盖返回地址,指向缓冲区本身的代码,这就是他们获取代码执行的方式。太恶心了。
当我最初在代码中初始化变量时会发生什么。它们是在代码段中还是在数据段中还是在堆中?
我将在这里从 C/C++ 的角度进行讨论。假设你有一个变量声明:
int i;
这会在堆栈上保留(通常)四个字节。如果你有:
char *buffer = malloc(100);
这实际上保留了两块内存。对 malloc 的调用在堆上分配 100 个字节。但是您还需要存储指针缓冲区。同样,该存储在堆栈上,在 32 位机器上将是 4 个字节(64 位机器将使用 8 个字节)。
数组存储在哪里...???
这取决于您如何声明它们。如果你做一个简单的数组:
char str[128];
例如,这将在堆栈上保留 128 个字节。除非您通过调用诸如 malloc 之类的分配方法明确要求它,否则 C 永远不会命中堆。
如果您声明一个指针(如上面的缓冲区),则 指针 的存储在堆栈上,数组的实际数据在堆上。
是不是在我的代码执行后,我堆中的所有内容都被删除了...???
基本上,是的。操作系统将在进程退出后清理进程使用的内存。堆是进程中的一块内存,因此操作系统会清理它。尽管这取决于您所说的“清理它”是什么意思。操作系统将这些 RAM 块标记为现在空闲,并将在以后重用它。如果您有明确的清理代码(如 C++ 析构函数),您需要确保它们被调用,操作系统不会为您调用它们。
总而言之,请以更简单的方式告诉我堆,而不是仅仅用于 malloc 和 alloc?
堆就像它的名字一样,是一堆空闲字节,你可以一次抓取一块,做任何你想做的事情,然后扔回去做其他事情。你通过调用 malloc 获取一大块字节,然后通过调用 free 将其扔回。
你为什么要这样做?嗯,有几个常见的原因:
你不知道有多少东西
您需要直到运行时(基于
例如用户输入)。那么你
在堆上动态分配为
你需要它们。
您需要大型数据结构。上
Windows,例如,一个线程的
堆栈默认限制为 1
梅格。如果您正在使用大型
位图,例如,这将是
快速炸掉你的筹码并获得
堆栈溢出。所以你抓住那个
堆的空间,通常是
比堆栈大得多。
代码、数据、栈和堆?
不是一个真正的问题,但我想澄清一下。 “代码”段包含应用程序的可执行字节。通常,代码段仅在内存中读取,以帮助防止篡改。数据段包含编译到代码中的常量 - 诸如代码中的字符串或数组初始化器之类的东西需要存储在某个地方,数据段就是它们所在的位置。同样,数据段通常是只读的。
堆栈是内存的可写部分,通常具有有限的大小。操作系统将初始化堆栈,C 启动代码会为您调用 main() 函数。堆也是内存的可写部分。它由操作系统保留,诸如 malloc 和 free 之类的功能管理从中取出块并将它们放回原处。
所以,这就是概述。我希望这会有所帮助。