C语言中的内存管理(栈、堆、全局静态区、常量区、代码区)

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)
代码区存储程序的实际机器码指令,即编译后的二进制可执行代码。通常情况下,代码区是只读的,以防止代码在运行时被意外修改,从而确保程序的稳定性和安全性。

作用:存储程序的机器码,即程序执行的指令。
特点:
        只读:为了避免程序指令被修改,代码区通常被设置为只读,这种设计提高了程序的安全性。
        程序指令:该区域存储所有函数和方法的机器码,是程序执行的核心部分。
生命周期:代码区随着程序的加载进入内存开始存在,并随着程序的终止而消失。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值