动态内存管理

目录

一、动态内存函数

1、malloc

2、free

3、calloc

4、realloc

二、常见动态内存错误

1、对NULL的解引用操作

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

3、对非动态开辟内存使用free释放

4、使用free释放一块动态开辟内存的一部分

5、对同一块动态内存多次释放

6、动态开辟内存忘记释放(内存泄漏)

三、经典笔试题

 笔试题一:

 笔试题二:

笔试题三:

笔试题四:

四、C/C++程序的内存开辟

五、柔性数组

1、柔性数组的特点:

 2、柔性数组的优势:


为什么会有动态内存分配呢?

动态内存管理_第1张图片

int a = 10;//在栈空间上开辟4个字节
char arr[10] = { 0 };//在栈空间上开辟10个字节的连续空间

 上述开辟空间的方式:

a.空间大小是固定的

b.数组声明时,需指定数组长度,它所需要的内存在编译时分配

但有时候所需空间大小在运行时才知道,就要用到动态内存开辟了。

一、动态内存函数

1、malloc

void* malloc (size_t size);

这个函数向内存申请一块连续可用的空间,并返回指向这块空间的指针。

a.开辟成功,则返回一个指向开辟好空间的指针

b.开辟失败,则返回一个NULL指针(要检查malloc的返回值)

c.返回值的类型是void*,malloc函数并不知道开辟空间的类型,要使用者自己强制类型转换

2、free

void free (void* ptr);

free用于释放指针所指向的动态内存

举个栗子:

#include
#include
#include
int main()
{
	int arr[5] = { 0 };
	//动态内存开辟
	int* p = (int*)malloc(sizeof(arr));
	//检查返回类型是否是NULL指针
	if (p == NULL)
	{
		printf("%s\n", strerror(errno));
	}
	//使用
	int i = 0;
	for (i = 0; i < 5; i++)
	{
		*(p + i) = i;
	}
	for (i = 0; i < 5; i++)
	{
		printf("%d ", *(p + i));
	}
	//free释放动态内存
	free(p);
	p = NULL;
	return 0;
}

3、calloc

void* calloc (size_t num, size_t size);

calloc函数的功能是为num个大小为size的元素开辟一块空间,并且在返回地址之前把申请空间的每个字节初始化为0(这是与malloc函数的区别)

举个栗子:

#include
#include
#include
int main()
{
	//动态内存开辟
	int* p = (int*)calloc(10, sizeof(int));
	//检查p是否为NULL指针
	if (p == NULL)
	{
		printf("%s\n", strerror(errno));
		return 1;
	}
	//打印
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		printf("%d ", *(p + i));
	}
	//释放
	free(p);
	p = NULL;
	return 0;
}

4、realloc

realloc函数可以做到对动态开辟内存大小的调整

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

ptr是要调整的内存地址

size是调整之后的新大小

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

realloc在调整内存空间的时候存在两种情况:

情况1:原有空间之后有足够大的空间

此时有扩展内存直接在原有内存之后追加空间,原来空间的数据不发生变化

动态内存管理_第2张图片

情况2:原有之后没有足够大的空间

此时在堆空间上另找一个合适大小的连续空间来使用,这样函数返回的是一个新的内存地址

动态内存管理_第3张图片

 举个栗子:

#include
#include
#include
int main()
{
	//动态开辟内存
	int* p = (int*)malloc(40);
	//检查返回值是否是空指针
	if (p == NULL)
	{
		printf("%s\n", strerror(errno));
		return 1;
	}
	//使用
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		*(p + i) = i + 1;
	}
	//扩容
	int* ptr = (int*)realloc(p, 80);
	//确认不是空指针
	if (ptr != NULL)
	{
		p = ptr;
	}
	//使用
	for (i = 0; i < 10; i++)
	{
		printf("%d ", *(p + i));
	}
	//释放
	free(p);
	p = NULL;
	return 0;
}

二、常见动态内存错误

1、对NULL的解引用操作

void test()
{
	int* p = (int*)malloc(40);
	*p = 20;//如果p的值是NULL,就会出现问题
	free(p);
	p = NULL;
}
int main()
{
	test();
	return 0;
}

错误纠正:

#include
#include
void test()
{
	int* p = (int*)malloc(40);
	if (p == NULL)
	{
		printf("%s\n", strerror(errno));
        return 1;
	}
	*p = 20;
	free(p);
	p = NULL;
}
int main()
{
	test();
	return 0;
}

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

#include
#include
void test()
{
	int* p = (int*)malloc(40);
	if (p == NULL)
	{
		printf("%s\n", strerror(errno));
		return 1;
	}
	//使用
	int i = 0;
	for (i = 0; i <= 10; i++)//i=10时,数组越界访问
	{
		*(p + i) = i;
	}
	free(p);
	p = NULL;
}
int main()
{
	test();
	return 0;
}

错误纠正:

#include
#include
void test()
{
	int* p = (int*)malloc(40);
	if (p == NULL)
	{
		printf("%s\n", strerror(errno));
		return 1;
	}
	//使用
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		*(p + i) = i;
	}
	free(p);
	p = NULL;
}
int main()
{
	test();
	return 0;
}

3、对非动态开辟内存使用free释放

void test()
{
	int a = 10;
	int* p = &a;
	free(p);
	p = NULL;
}
int main()
{
	test();
	return 0;
}

4、使用free释放一块动态开辟内存的一部分

#include
#include
void test()
{
	//动态内存开辟
	int* p = (int*)malloc(40);
	//检查p是否为NULL
	if (p == NULL)
	{
		printf("%s\n", strerror(errno));
		return 1;
	}
	//使用
	int i = 0;
	for (i = 0; i < 5; i++)
	{
		*p = i;
		p++;
	}
	//释放
	free(p);
	p = NULL;
}
int main()
{
	test();
	return 0;
}

动态内存管理_第4张图片

 此时p不再指向动态内存的起始位置

5、对同一块动态内存多次释放

int main()
{
	int* p = (int*)malloc(40);
	free(p);
	free(p);
}

错误纠正:

int main()
{
	int* p = (int*)malloc(40);
	free(p);
	p = NULL;
	free(p);
}

6、动态开辟内存忘记释放(内存泄漏)

void test()
{
	int* p = (int*)malloc(40);
	int flag = 0;
	scanf("%d", &flag);
	if (flag == 5)
	{
		return;
	}
	free(p);
	p = NULL;
}
int main()
{
	test();
	return 0;
}

如果输入5的话就忘记释放动态内存空间,造成内存泄露。

int* test()
{
	int* p = (int*)malloc(40);
	if (p == NULL)
	{
		return p;
	}
	return p;
}
int main()
{
	int* ret = test();
	return 0;
}

如上代码的情况也容易释放动态内存。

所以,一定要记得正确释放动态开辟的空间。

三、经典笔试题

 笔试题一:

void GetMemory(char* p)
{
	p = (char*)malloc(100);//没有使用free释放动态内存空间,造成了内存泄露
}
void Test(void)
{
	char* str = NULL; 
	GetMemory(str);
	strcpy(str, "hello world");//形参p的值并没有带给str,str是NULL,解引用时程序会崩溃
	printf(str);
}
int main()
{
	Test();
	return 0;
}

动态内存管理_第5张图片

改正方法一:

void GetMemory(char** p)
{
	*p = (char*)malloc(100);
}
void Test(void)
{
	char* str = NULL;
	GetMemory(&str);
	strcpy(str, "hello world");
	printf(str);
	free(str);
	str = NULL;
}
int main()
{
	Test();
	return 0;
}

 改正方法二:

char* GetMemory(char* p)
{
	p = (char*)malloc(100);
	return p;
}
void Test(void)
{
	char* str = NULL;
	str = GetMemory(str);
	strcpy(str, "hello world");
	printf(str);
	free(str);
	str = NULL;
}
int main()
{
	Test();
	return 0;
}

动态内存管理_第6张图片

 笔试题二:

char* GetMemory(void)
{
	char p[] = "hello world";
	return p;
}
void Test(void)
{
	char* str = NULL;
	str = GetMemory();
	printf(str);
}
int main()
{
	Test();
	return 0;
}

动态内存管理_第7张图片

以上属于返回栈空间的地址问题 。

笔试题三:

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

这道题确实用了传址调用,但却没有用free释放内存空间。

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

动态内存管理_第8张图片

笔试题四:

void Test(void)
{
	char* str = (char*)malloc(100);
	strcpy(str, "hello");
	free(str);
	if (str != NULL)
	{
		strcpy(str, "world");//str是野指针
		printf(str);
	}
}
int main()
{
	Test();
	return 0;
}

 str指向的空间释放,但没有及时使置str=NULL。

四、C/C++程序的内存开辟

动态内存管理_第9张图片

其中:

栈区:主要存放运行函数而分配的局部变量、函数参数、返回数据、返回地址等。
 堆区:一般由程序员分配释放。。

数据段(静态区):存放全局变量、静态数据。

代码段:存放函数体(类成员函数和全局函数)的二进制代码。

五、柔性数组

柔性数组成员:结构体中最后一个元素允许是未知大小的数组。

例如:

	struct Struct
	{
		int i;
		int arr[0];//柔性数组成员,0表示未知大小
	};
	struct Struct
	{
		int i;
		int arr[];//柔性数组成员
	};

1、柔性数组的特点:

a.柔性数组成员前必须至少有一个其他成员

b.sizeof返回的结构体大小不包括柔性数组的内存

例如:

struct Struct
{
	int i;
	int arr[0];
};
int main()
{
	printf("%d\n", sizeof(struct Struct));//结构体的大小是4
	return 0;
}

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

例如:

动态内存管理_第10张图片

struct S
{
	int i;
	int arr[];
};
int main()
{
	//柔性数组的使用
	struct S* p = (struct S*)malloc(sizeof(struct S) + 40);
	if (p == NULL)//检查
	{
		return 1;
	}
	p->i;
	for (int k = 0; k < 10; k++)
	{
		p->arr[k] = k;
	}
	//扩容
	struct S* p1 = (struct S*)realloc(p, sizeof(struct S) + 80);
	if (p1 == NULL)
	{
		p = p1;
		return 1;
	}
	free(p);//释放
	p = NULL;
	return 0;
}

 2、柔性数组的优势:

先看案例:

typedef struct S
{
	int i;
	int* a;//将柔性数组改为指针的形式
}S;
int main()
{
	S* p = (S*)malloc(sizeof(S));//开辟
	if (p == NULL)//检查
	{
		return 1;
	}
	p->i = 100;
	p->a = (int*)malloc(40);//开辟
	if (p->a == NULL)
	{
		return 1;
	}
	//使用
	for (int k = 0; k < 10; k++)
	{
		p->a[k] = k;
	}
	//扩容
	int* p1 = (int*)realloc(p->a, 80);
	if (p1 == NULL)
	{
		return 1;
	}
	//释放
	free(p->a);
	p->a = NULL;
	free(p);
	p = NULL;
	return 0;
}

动态内存管理_第11张图片

 其实使用柔性数组的形式更好,原因如下:

a.如果我们把结构体的内存以及其成员要的内存一次性分配好,返回给用户一个结构体指针,用户做一次free就可以把所有的内存也给释放掉

b.有益于减少内存碎片,提高访问速度

动态内存管理_第12张图片 

本次分享到此结束,后续会有不断更新,不要忘记一键三连噢~ 

你可能感兴趣的:(C语言,c语言)