在本教程中,我们将探索过程内存,以更好地了解C中的内存是如何分配的。开始,让我们看一下过程的一般内存结构。
堆栈内存
调用函数时,堆栈内存会增长以容纳函数的堆栈框架。当功能返回时,堆栈存储器在函数的堆栈框架上弹出时的大小会缩小。堆栈内存从内存的高端开始,向下生长,朝堆。
您的处理器中的一个特殊目的寄存器称为堆栈指针,跟踪堆栈的当前顶部。堆栈上的每个堆栈框架都包含函数参数,本地变量和呼叫链接信息,例如该功能的返回地址。由于可以在功能中调用函数,因此我们通常可以在堆栈上看到多个帧。
在C中,我们通常不必担心过多的细节堆栈内存。了解功能帧的工作方式提供了重要的环境,但是我们通常不会直接与堆栈进行交互。当我们在C中分配内存时,我们正在与堆进行交互,我们将向下一次查看。
堆内存
堆是一个动态的内存区域,它向堆栈生长。当您在程序中分配内存时,该内存将分配在堆上。要分配内存,我们通常使用 malloc ,它基于另一组函数, brk() 和 sbrk()。这些功能通过调整程序断开大小来起作用,该功能是代表堆内存结束和未分配内存的开始的内存位置。
BRK和SBRK
调整过程堆的大小是一项简单的任务。我们只需要告诉内核调整程序中断的位置(定义为堆的当前顶部)。由于通过程序中断的内存是未分配的内存,因此我们可以将突破移入未分配的内存中,为我们的过程分配一些新的内存。此过程有两个功能:
- brk(void *end_data_segment):将程序中断设置为 end_data_segment 指定的位置。
- sbrk(intptr_t增量):增量程序的大小通过增量。 。
要查看这些功能的工作原理,让我们看一个简单的示例。
#include <unistd.h>
#include <stdio.h>
int main(int argc, char *argv[]){
int *currentBreak = sbrk(0);
printf("%p\n",currentBreak);
}
在此示例中,向SBRK提供0的增量。当向SBRK提供0的增量时,返回了当前的中断地址。
如果您提供了0至 sbrk 以外的值,则获得了先前的中断地址,这意味着在更改中断之前的地址。我们可以使用连续的一些增量看到这种效果。
#include <unistd.h>
#include <stdio.h>
int main(int argc, char *argv[]){
void *currentBreak = sbrk(0x5);
printf("First increment of 0x5: %p\n",currentBreak);
currentBreak = sbrk(0x5);
printf("Second increment of 0x5: %p\n",currentBreak);
currentBreak = sbrk(0x5);
printf("Third increment of 0x5: %p\n",currentBreak);
currentBreak = sbrk(0x5);
printf("Fourth increment of 0x5: %p\n",currentBreak);
currentBreak = sbrk(0x5);
printf("Fifth increment of 0x5: %p\n",currentBreak);
}
运行这组增量时,您将获得类似于下图的结果。
请注意,第一和第二增量之间的断裂大于我们指定的0x5。这是因为printf分配内存以用作Stdout的缓冲区。最初发生这种情况时,我们会看到堆断裂发生了很大的变化。第三,第四和第五增量通过增量0x5始终如一地移动,因为它们之间没有其他数据分配其他数据。
在大多数情况下,我们不使用 sbrk 和 brk 直接调整C中的堆被称为 malloc 和 free 的过程。在下一篇文章中,我们将更详细地探讨这些功能!