C语言中内存分为5部分(栈、堆、全局静态区、常量区、代码区),不同的区域,有不同的用途,有不同的管理策略,生命周期也不一样。
1. 栈(Stack)
栈是一种后进先出的数据结构。程序执行时,每当进入一个函数,函数的局部变量和参数都会被压入栈中。当函数返回时,栈帧会被弹出,内存随之释放。
栈内存的管理由编译器自动完成,这提高了内存操作的效率,使得栈成为存储局部变量和函数参数的理想位置。以下是对栈的详细介绍:
作用:存储局部变量、函数参数、返回地址等。函数调用过程中,栈会自动分配和回收内存。
特点:
自动管理:局部变量在函数调用开始时自动分配,在函数返回时自动释放。这种特性使得栈操作非常高效。
快速分配:由于栈是LIFO结构,新的变量分配只是简单地移动栈指针,因此分配速度非常快。
受限容量:栈空间通常较小,过多的递归调用或者分配大数组可能会导致栈溢出(Stack Overflow),从而引发程序崩溃。
生命周期:栈上变量的生命周期与其所在函数的执行时间相同,即变量在函数执行期间存在,当函数返回后,变量也就消失。
示例代码:
#include <stdio.h>
#include <string.h>
#include <unistd.h>
void * demo(int a, int b)
{
int res;
int * sumP;
res = (a + b);
sumP = &res;
printf("demo函数中sum=%d\n",res);
return sumP;
}
int main()
{
int a = 15;
int b = 35;
int * sum;
sum = demo(a, b);
sleep(2);
printf("main函数中sum=%d\n",*sum);
return 0;
}
代码结果如下,res是一个局部变量,demo函数调用结束时,内存也就被释放
2. 堆(Heap)
堆是一种自由存储区,允许程序在运行时动态分配和释放内存。与栈不同,堆并不自动管理内存,需要程序员显式地通过函数如malloc和free来进行管理。
作用:用于动态内存分配。通过函数malloc、calloc、realloc来分配,通过free来释放。
特点:
灵活性:可以分配任意大小的内存,用于不可预见的或规模较大的数据。
管理复杂:需要手动管理内存,容易出现内存泄漏(allocated memory not freed)和碎片化(fragmentation)。
生命周期:由程序员控制,直到调用free释放。
动态内存分配的好处及风险:
好处:
可以按需分配所需大小的内存,有助于处理不确定大小的数据。
能够在运行时灵活地管理内存,提高内存利用效率。
风险:
需要显式管理内存,容易出现内存泄漏。
如果忘记释放内存,可能造成内存不足。
内存分配和释放的不匹配容易导致程序崩溃或行为异常。
通过合理使用堆内存,可以有效地管理大量数据,但需要注意内存管理的细节,以避免 常见的内存问题。
示例代码:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
/*
mallloc 函数原型 void * malloc(size_t size),分配所需的内存空间,并返回一个指向它的指针
注意函数是void,所以返回后按需强转
realloc 函数原型 void * realloc(void *ptr size_t size)
c库函数void *realloc(void *ptr size_t size),尝试重新调整之前调用calloc,mcalloc,
所分配的ptr所指向的内存块大小
free C库函数void free(void *ptr),释放之前调用 calloc,mcalloc或 realloc所分配的内存
释放,防止内存泄漏,防止悬挂指针(野指针的一种)
*/
int main()
{
char *p; //没有指向,是个野指针
p = (char *)malloc(12); //分配指定大小的内存空间
strcpy(p, "hello");
puts(p);
free(p); //释放之前调用calloc,mcalloc或 realloc所分配的内存
p = (char *)malloc(12);
strcpy(p,"nihao");
puts(p);
free(p);
p = (char *)malloc(12);
strcpy(p,"nihao");
memset(p, '\0', 12); //memset将之前内存空间的内容都置为 '\0'
free(p);
//分配内存为5的内存空间,肯定装不下,所以要扩容,原有的内存上扩容
p = (char *)malloc(5);
int len = strlen("nihao123456789");
int newlen = len - 5 + 1;
realloc(p, newlen); //memset将之前内存空间的内容都置为 '\0'
strcpy(p,"nihao123456789");
puts(p);
free(p);
return 0;
}
3. 全局/静态区(Global/Static)
该区域存储全局变量和静态变量,这些变量在程序启动时即已分配内存,并在整个程序运行期间都存在。全局变量在全部代码中可见,而静态变量仅在其定义的作用域内可见但有全局生命周期。
作用:存储全局变量、静态变量。
特点:
程序生命周期内存储数据:适用于需要在整个程序运行期间保持状态的数据。
多文件问题:在多文件程序中需要注意变量重复定义问题,通常使用extern关键字声明外部变量。
生命周期:自程序开始运行,直到程序结束。
示例代码:代码示例和栈代码示例一样,函数内部变量声明为静态变量
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
void * demo(int a, int b)
{
static int res;
int * sumP;
res = (a + b);
sumP = &res;
printf("demo函数中sum=%d\n",res);
return sumP;
}
int main()
{
int a = 15;
int b = 35;
int * sum;
sum = demo(a, b);
sleep(2);
printf("main函数中sum=%d\n",*sum);
return 0;
}
代码结果如下:
4. 常量区(Text)
常量区通常存储常量数据,例如字符串字面量,这些数据在程序运行时不可改变。如果尝试修改,会引发未定义行为或程序崩溃。
作用:存储常量数据,比如字符串字面量。
特点:
只读:常量区的内容通常设为只读,以防止意外修改。
数据共享:字符串字面量可能被多个部分共享,节省内存开销。
生命周期:与程序运行期一致。
代码示例:
#include <stdio.h>
int main() {
const char *text = "Hello"; // 存储在常量区
printf("Text: %s\n", text);
// text[0] = 'h'; // 错误: 尝试修改常量区会导致未定义行为
return 0;
}
5. 代码区(Code)
代码区存储程序的实际机器码指令,即编译后的二进制可执行代码。通常情况下,代码区是只读的,以防止代码在运行时被意外修改,从而确保程序的稳定性和安全性。
作用:存储程序的机器码,即程序执行的指令。
特点:
只读:为了避免程序指令被修改,代码区通常被设置为只读,这种设计提高了程序的安全性。
程序指令:该区域存储所有函数和方法的机器码,是程序执行的核心部分。
生命周期:代码区随着程序的加载进入内存开始存在,并随着程序的终止而消失。