深入解析堆与栈的区别:全局堆、局部堆及线程栈的详细比较
首先我们来科普一下:

什么是堆?说到堆,又忍不住说到了栈!什么是 栈?
栈是什么:栈是线程所独有的,它用于保存线程的运行状态以及局部自动变量。栈在线程开始时进行初始化,各个线程的栈是相互独立的。每个函数都拥有其自身的栈,栈被用于在函数之间传递参数。操作系统在进行线程切换时,会自动地切换栈,也就是切换 SS/ESP 寄存器。栈空间无需在高级语言中进行显式的分配和释放操作。
堆通常是由程序员来进行分配和释放的,如果程序员不进行释放,那么在程序结束的时候,可能会由操作系统来进行回收。需要注意的是,这里说的是可能回收,而不是一定回收。再次强调,一定要记得进行释放操作!
栈区(stack) :
在 Windows 下,栈内存被分配了 2M,这是一个确定的常数。当超出这个限制时,就会提示 stack overflow 错误。
编译器会自动进行分配和释放。它主要用来存放函数的参数值以及局部变量值等。
堆区(heap):程序员手动分配释放,操作系统80%内存
全局区或静态区:这里会存放全局变量以及静态变量;在程序结束的时候,会由系统来进行释放;它又分为全局初始化区和全局未初始化区;
字符常量区:常量字符串放与此,程序结束时由系统释放;
程序代码区:存放函数体的二进制代码。
栗子:
定义整型变量 a 并初始化为 0,a 位于全局初始化区;定义字符变量 p1,p1 位于全局未初始化区;在主函数 main 中,定义整型变量 b,b 位于栈区;定义字符数组 s 并初始化为 "bb",s 位于栈区;定义字符变量 p2,p2 位于栈区;定义字符变量 p3 并初始化为 "123",其中 "123\0" 位于常量区,p3 位于栈区;定义静态整型变量 c 并初始化为 0,c 位于全局区;通过 malloc 函数分配 10 个字节的区域给 p1,该区域位于堆区;使用 strcpy 函数将 "123" 复制到 p1 中,"123\0" 位于常量区,编译器可能会优化为和 p3 的指向同一块区域。
栈内存
在函数 stackFun 中:定义了一个大小为 1024 的整型数组 a;栈内存会自动释放。
堆内存
最后使用`free`函数释放了分配的内存`p`。
在堆内存中进行分配操作,分配的内存大小为 40M。一直处于循环状态,循环条件为 1,在每次循环中会使程序暂停 1000 毫秒,然后执行 stackFun 函数。最后通过 getchar 函数获取字符输入。
创建一个数组,在程序运行过程中可以动态指定其大小,能够随意开辟指定大小的内存以供使用,这就如同 Java 中的集合。静态内存分配时,分配的内存大小是固定的。存在的问题如下:其一,很容易超出栈内存的最大值;其二,为防止内存不够用而开辟更多内存,容易导致内存浪费。
在程序运行期间,进行动态内存分配,可动态地指明需要使用的内存的大小,并且需要手动进行释放,当释放之后,这些内存是可以再次被使用的。
函数:calloc() 分配内存空间并进行初始化。calloc() 函数用于动态地分配内存空间且初始化为 0 ,它的原型为:
调用 calloc 函数,该函数需要传入两个参数,一个是元素的数量 num,另一个是每个元素的大小 size。
calloc() 在内存中进行动态分配。它分配的是 num 个长度为 size 的连续空间。并且会将每一个字节初始化为 0。所以它的结果是分配了 numsize 个字节长度的内存空间,且每个字节的值都为 0。
分配成功时会返回指向该内存的地址,而分配失败时则会返回 NULL。
函数:malloc() 动态地分配内存空间
malloc()函数用于动态地分配内存空间。如果你不了解动态内存分配,可查看:C 语言动态内存分配及变量存储类别。其原型为:
void* malloc (size_t size);
应用在程序中代码如下:
在 main 函数中,通过静态内存分配来创建数组,此数组的大小是固定的。首先定义了一个变量 i 并赋值为 10,接着又定义了一个数组 a,其大小由变量 i 决定。然后要求用户输入数组的长度,通过 scanf 函数获取用户输入的值并存储在变量 len 中。接着使用 malloc 函数开辟内存,大小为 len 乘以 sizeof(int),得到的内存地址存储在变量 p 中,p 即为数组的首地址,也就相当于数组的名称。之后通过一个循环,从 i 为 0 开始,当 i 小于 len 时,给数组元素赋值,使用的就是刚刚开辟出来的内存区域。< len - 1; i++){p[i] = rand() % 100;使用 printf 函数输出 p 数组中第 i 个元素的值及其地址的十六进制表示形式,分别以十进制整数和十六进制数的形式输出,输出格式为"%d,%#x\n",其中 p[i] 表示数组 p 中的第 i 个元素,&p[i] 表示 p 数组中第 i 个元素的地址。 }//手动释放内存 //free()释放动态分配的内存空间 free(p); getchar();}
realloc 重新分配内存
接着进入一个循环,只要 `i` 小于 `len` 就执行循环体中的操作。< len; i++){p[i] = rand() % 100;printf("%d,%#x\n", p[i], &p[i]); } int addLen; printf("输入数组增加的长度:"); scanf("%d", &addLen); //内存不够用,扩大刚刚分配的内存空间 //1.原来内存的指针 2.内存扩大之后的总大小int* p2 = realloc(p, sizeof(int) * (len + addLen)); if (p2 == NULL){printf("重新分配失败,世界那么大,容不下我。。。"); }
新分配内存的两种情况:
//缩小,缩小的那一部分数据会丢失
如果申请失败,就返回 NULL,而原来的指针仍然有效。
接着上面的代码进行重新赋值,先令 i 等于 0 ;然后输出“--------------------------”;接着进入循环,在循环条件中,当 i 满足一定条件时执行循环体内容。< len + addLen; i++){p2[i] = rand() % 200;printf("%d,%#x\n", p2[i], &p2[i]); } //手动释放内存 if (p != NULL){free(p);p = NULL; }if (p2 != NULL){free(p2);p2 = NULL; } getchar();}
内存分配的几个注意细节
不能进行多次释放操作;释放完成后(即便指针仍有值),需将指针置为 NULL,以此标志释放已完成;存在内存泄露情况,即当 p 重新赋值后再进行 free 操作,实际上并未真正释放内存。
void main(){
int len;
printf("输入数组的长度:");
scanf("%d", &len);
分配了一块内存空间,该内存空间的大小为 len 乘以 sizeof(int),并将指向这块内存空间的指针赋值给了指针变量 p,p 是一个指向 int 类型的指针。
int i = 0;
for (; i < len; i++){
p[i] = rand() % 100;
printf("%d,%#x\n", p[i], &p[i]);
if (p != NULL){
free(p);
p = NULL;
getchar();