C语言进阶——动态内存管理

本文详细介绍了C/C++中的动态内存管理,包括malloc、free、calloc、realloc函数的使用,内存错误与常见问题,以及柔性数组的概念。通过实例演示了如何避免内存泄漏和正确处理动态内存的分配与释放。
摘要由CSDN通过智能技术生成

一,动态内存管理介绍

1.1 为什么要有动态内存分配

我们已经熟悉的内存开辟的方式有

int a= 10;   变量
int arr[10]; 数组

但是上面两种方式有两个缺点:1,空间开辟大小是固定的 2,数组在声明的时候必须指明数组长度,在编译时分配

但是对于空间的需求,不仅仅时上面的情况,有时候我们对内存的需求是会随着程序的依次执行而改变的,于是上面两种方式就不适合了,于是C语言推出了动态开辟的内存管理方式 

1.2 malloc和free

void* malloc (size_t size);
void free (void* ptr);

这个函数可以向内存申请一块连续可用的空间,并返回指向这块空间的指针,有以下几点规则:

①开辟成功后,返回一个指向该空间的指针,该指针位void*类型,所以有时候需要强转

②如果开辟失败,返回一个NULL指针,因此malloc的反返回值一定要做检查

③如果参数size为0,malloc的行为标准是未定义的,取决于编译器

④free是专门用来释放和回收动态开辟出来的内存的,如果参数为NULL,那么free什么也不做

#include<stdlib.h>
#include<stdio.h>
//malloc申请到空间后,只会返回这块空间的起始地址,不会初始化空间的内容,而且申请后不会自动释放,需要手动free
void main1()
{
	int* p = (int*)malloc(40);
	if (p == NULL)
	{
		perror("malloc");
		return 1;
	}
	//开辟成功
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		printf("%d\n", *(p + i));
	}
	free(p);//释放
	p = NULL;
}

1.3 calloc

void* calloc (size_t num,size_t size);

 该函数功能是为num个大小为size的元素开辟一块空间,并且把该空间的每个字节均初始化为0

void main2()
{
	//给我开出10个整形大小的空间
	int* p = (int*)calloc(10, sizeof(int));
	//int* p = (int*)calloc(INT_MAX, sizeof(int)); INT_MAX太大,会开辟失败
	if (p == NULL)
	{
		perror("calloc");
		return 1;
	}
	//打印数据
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		printf("%d ", p[i]);
	}
	//释放
	free(p);
	p = NULL;
}

调试时打开内存窗口查看p的值时,会发现被初始化成0

1.4 realloc

void* realloc (void& ptr,size_t size);

有时候我们发现前面申请的空间太小了或者太大了,为了合理地使用内存,我们一定会对申请的内存的大小做调整,所以realloc这个函数的功能就是这样,让动态内存管理更加灵活

对于realloc也有以下几点规则:

①ptr是要调整的内存的地址,size调整后的大小

②返回值为调整之后的的新内存的起始位置

③这个函数在调整原内存空间大小的基础上,还会将原来内存中的数据移动到新的空间里去

realloc在调整内存空间有两种情况:

①原有空间后面有足够大的空  ②原空间后没有足够大空间 

 如果是情况①,要扩展内存就直接原有内存后直接追加空间,原来空间的数据不发生变化,如果是情况②的时候,会在堆空间上找一块合适大小的连续空间来使用,这样函数返回的是一个新的内存地址

void main3()
{
	int* p = (int*)malloc(40);
	if (p == NULL)
	{
		perror("malloc");
		return 1;
	}
	//初始化为1~10
	int i = 0;
	for (i = 0; i < 10; i++) 
	{
		p[i] = i + 1;
	}
	//增加一些空间
	int* ptr = (int*)realloc(p, 8000);
	if (ptr != NULL)
	{
		p = ptr;
		ptr = NULL;
	}
	else
	{
		perror("realloc");
		return 1;
	}
	//打印数据
	for (i = 0; i < 20; i++)
	{
		printf("%d\n", p[i]);
	}
	//释放
	free(p);
	p = NULL;
}

1.5 常见动态内存错误

1.5.1 对NULL指针的解引用

void test()
{
    int* p=(int*)malloc(INT_MAX/4);
    *p = 20; //INT_MAX太大了,会开辟失败返回NULL,这时候就会错误
    free(p);
}

1.5.2 对动态开辟空间的越界访问

void main4()
{
	int* p = (int*)malloc(40);
	if (p == NULL)
	{
		perror("malloc");
		return 1;
	}
	int i = 0;
	//对动态开辟空间的越界访问
	for (i = 0; i < 20; i++)
	{
		p[i] = i;//越界访问
	}
	free(p);
	p = NULL;
}

1.5.3 对非动态内存使用free释放

void main5()
{
	int a = 10;
	int* p = &a;
	printf("%d\n", *p);
	free(p);
	p = NULL;
}

1.5.4 对一块内存多次释放

void main6()
{
	int* p = (int*)malloc(40);
	if (p == NULL)
	{
		return 1;
	}
	//使用
	free(p);
	p = NULL;
	free(p);
}

1.5.5 忘记释放内存(内存泄漏)

void test()
{
	int* p = (int*)malloc(100);
	if (NULL != p)
	{
		*p = 20;
	}
	//下面while循环,可以让程序永远不结束
	//而对于上面的部分,我申请了100个字节空间然后用p指向它
	//但是函数结束后,我没有free掉这块空间
	//p是变量,存在栈帧中,函数结束会销毁,但是申请的100字节空间不会销毁
	//造成内存泄漏 -- 所以一定要记得释放
}

void main7()
{
	test();
	while (1);
}

二,关于动态内存的经典题目

题目一

//问:Test函数执行后结果如何
void GetMemory(char* p)
{
    p = (char*)malloc(100);
}
void Test(void)
{
    char* str = NULL;
    GetMemory(str);
    strcpy(str,"hello world");
    printf(str);
}

 我们按照题目要求执行Test后程序会直接挂掉,不会打印任何数据,因为把str传参传给p,然后给p一个100字节空间,但是p是形参,出了函数后会销毁,p销毁了不关str的事所以str依旧是NULL,对NULL拷贝数据就会报错

而且p销毁后开出的100字节空间没有释放,也会造成内存泄漏

解决办法如下

//void GetMemory(char* p)
void GetMemory(char** p)
{
	*p = (char*)malloc(100);
	//p = (char*)malloc(100);
	//原题目会挂掉,因为p是形参,函数结束后销毁,所以和下面的str没关系,str还是空指针
}

void Test(void)
{
	char* str = NULL;
	GetMemory(&str);
	//GetMemory1(str);
	strcpy(str, "hello world");
	printf(str);
	//原题目没有释放,会出现内存泄漏
	free(str);
	str = NULL;
}

题目二

char* GetMemory(void)
{
	char p[] = "hello world";
	return p;
}

void Test(void)
{
	char* str = NULL;
	str = GetMemory();
	printf(str);
}

这题和上面那一题差不多,p被创建,里面有hello world,出函数的时候p传给str,但是hello world不是动态开辟出来的,属于GetMemory函数栈帧里的数据,出函数后会被销毁,也就是p指向的空间被销毁了,于是通过str打印数据的时候会打印“烫烫烫烫” 

解决办法如下,将p定义为静态

char* GetMemory(void)
{
	static char p[] = "hello world";
	return p;
}

void Test(void)
{
	char* str = NULL;
	str = GetMemory();
	printf(str);
}

题目三

void GetMemory3(char** p, int num)
{
	*p = (char*)malloc(num);
}
void Test(void)
{
	char* str = NULL;
	GetMemory3(&str, 100);
	strcpy(str, "hello");
	printf(str);
}

这题没有什么语法或逻辑错误,单纯的就是忘记释放空间了,在打印str后free(str)并把str赋值为NULL即可 

void GetMemory3(char** p, int num)
{
	*p = (char*)malloc(num);
}
void Test(void)
{
	char* str = NULL;
	GetMemory3(&str, 100);
	strcpy(str, "hello");
	printf(str);
    free(str);
    str = NULL;
}

题目四

void Test()
{
	char* str = (char*)malloc(100);
	strcpy(str, "hello");
	free(str);
	if (str != NULL)
	{
		strcpy(str, "world");
		printf(str);
	}
}

这题有点棘手,因为free在前面就释放了,所以可能会有人说是提前释放的问题,但是如果是提前释放的原因的话,后面又加了个if判断,逻辑上说不过去

要记住,free做的只是释放空间,但是指向该空间的指针没有被销毁,这个指向已经被销毁的空间的指针就是传说中的野指针,所以free指针后最好把该指针也赋值为NULL,这样才能完美保证内存泄漏和内存安全

void main12()
{
	char* str = (char*)malloc(100);
	strcpy(str, "hello");
	free(str); //释放了,但是str不为NULL哦
	//题目没有置为空,所以free后尽量把释放的指针变量置为空
	str = NULL;

	if (str != NULL)
	{
		strcpy(str, "world");
		printf(str);
	}
}

三,C/C++的内存开辟

先看以下代码和运行结果:

int d = 200;
void main13()
{
	int a = 10;
	int b = 20;
	static int c = 100;
	printf("&a = %p\n", &a);
	printf("&b = %p\n", &b);
	printf("&c = %p\n", &c);
	printf("&d = %p\n", &d);
}

 ①栈区(stack):在执行函数时,函数内的局部变量都可以在栈上创建,每个函数都有属于自己的函数栈帧,函数结束时这些空间会自动释放。栈内存分配运算内置于处理器的指令集中,效率很高,对于需要频繁申请释放的局部变量非常适合,但是分配的内存容量有限。栈区主要存放运行函数而分配的局部变量,函数参数,返回数据,返回地址等

②堆区(heap):一般由程序员分配释放,若程序员不释放,程序结束后由OS回收

③数据段(静态区):存放全局变量,静态数据,程序结束后由系统释放

④代码段:存放函数体的二进制代码

说多了没啥用,直接看下面这个图吧

四,柔性数组

C99标准中,规定结构体的最后一个元素允许是未知大小的数组,这个数组叫做柔性数组

struct S
{
	int n;
	//int arr[];//柔性数组,前面至少有一个成员
	int arr[0]; //柔性数组
};

柔性数组顾名思义,是能改变自身大小的数组,它的规则如下:

①结构体中的柔性数组成员前面必须至少有一个其他的成员变量

②sizeof计算结构体大小时不计算柔性数组的大小

③包含柔性数组成员的结构用malloc()函数进内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小

下面是柔性数组的使用:
struct S
{
	int n;
	int arr[0];
};

void main14()
{
	printf("%d\n", sizeof(struct S));
	struct S* ps = (struct S*)malloc(sizeof(struct S) + 40); 
	//在堆上开44空间,4给n,40给arr
	if (ps == NULL)
	{
		perror("malloc");
		return 1;
	}
	ps->n = 100;
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		ps->arr[i] = i + 1;
	}

	//空间不够,需要增容
	struct S* ptr = realloc(ps, sizeof(struct S) + 60);
	if (ptr == NULL)
	{
		perror("realloc");
		return 1;
	}
	ps = ptr;
	ps->n = 15;
	for (i = 0; i < 15; i++)
	{
		printf("%d\n", ps->arr[i]);
	}

	//释放
	free(ps);
	ps = NULL;
}

下面是另一种用法

struct S1
{
	int n;
	int* arr;
};
void main15()
{
	struct S1* ps = (struct S1*)malloc(sizeof(struct S1));
	if (ps == NULL)
	{
		perror("malloc->ps");
		return 1;
	}
	ps->n = 100;
	ps->arr = (int*)malloc(40);//1 2 3 4 5 6 7 8 9 10
	if (ps->arr == NULL)
	{
		perror("malloc->arr");
		return 1;
	}
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		ps->arr[i] = i + 1;
	}

	//调整
	int* ptr = (int*)realloc(ps->arr, 60);
	if (ptr != NULL)
	{
		ps->arr = ptr;
	}
	else
	{
		perror("realloc");
		return 1;
	}
	//打印
	for (i = 0; i < 15; i++)
	{
		printf("%d\n", ps->arr[i]);
	}

	//释放
	free(ps->arr);
	ps->arr = NULL;

	free(ps);
	ps = NULL;
}

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值