文章目录
前言
动态内存管理是今后学习数据结构的基础,它弥补了之前学习一般数组的缺点,即不能按需使用内存:数组在初始化时的大小就已经被确定了。这种规定虽然提高了安全性,但对合理高效地使用内存不利,这篇文章将详细讲解几种动态内存管理函数、讲解经典笔试题以加深理解、介绍C/C++内存开辟的特点以及柔性数组的使用
1. 为什么存在动态内存分配
我们已经掌握的内存开辟方式有:
//1. 创建一个变量
int a = 20;//在栈空间上开辟四个字节
//2. 创建一个数组
char arr[10] = { 0 };//在栈空间上开辟10个字节的连续空间
但是上述的开辟空间的方式有两个特点:
 
    int num = 0;
    scanf("%d", &num);
    //这种写法是不被允许的
    
注意:
动态内存分配是在堆区上处理的

2. 动态内存函数
2.1 malloc和free
malloc要和free配对使用
malloc和free都声明在stdlib.h 头文件中。
2.1.1 malloc
void* malloc (size_t size);//字节

要点
2.1.2 free
void free (void* ptr);

要点
2.1.3 用例
#include <stdio.h>
int main()
{
	
	
	int* ptr = NULL;//初始化指针
	ptr = (int*)malloc(1000);
	if (NULL != ptr)//判断ptr指针是否为空
	{
		int i = 0;
		for (i = 0; i < num; i++)
		{
			*(ptr + i) = 0;
		}
	}
	free(ptr);//释放ptr所指向的动态内存
	ptr = NULL;//free掉以后一定要将指针置空
	return 0;
}
要点
- 开辟内存的不同写法
ptr = (int*)malloc(1000);
ptr = (int*)malloc(100 * sizeof(int));

	free(ptr);//释放ptr所指向的动态内存
	ptr = NULL;//free掉以后一定要将指针置空
2.2 calloc
void* calloc (size_t num, size_t size);

要点
举个例子:
#include <stdio.h>
#include <stdlib.h>
int main()
{
	int* p = (int*)calloc(10, sizeof(int));
	if (NULL != p)
	{
		for (int i = 0; i < 10; i++)
		{
			printf("%d ", *(p + i));//打印
		}
	}
	free(p);
	p = NULL;
	return 0;
}

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

要点
- 情况1:原有空间之后有足够大的空间
- 情况2:原有空间之后没有足够大的空间

由于上述的两种情况,realloc函数的使用就要注意一些。
3. 常见错误
3.1 对NULL指针解引用
void test()
{
  int num = 0;
	scanf("%d", &n);
	int* p = (int*)malloc(num);
	*p = 20;//如果p的值是NULL,就会有问题
	free(p);
}
改进
void test()
{
  int num = 0;
	scanf("%d", &n);
	int* p = (int*)malloc(num);
	if(p != NULL)//判断指针是否为空
	*p = 20;
	free(p);
}
3.2 越界访问动态开辟空间
void test()
{
	int i = 0;
	int* p = (int*)malloc(10 * sizeof(int));
	//这里只开辟了10个int大小的空间
	if (NULL == p)
	{
		return 0;
	}
	for (i = 0; i <= 10; i++)
	{
		*(p + i) = i;//循环11次,当i是10的时候越界访问
	}
	free(p);
}
3.3 使用free释放非动态开辟内存
void test()
{
	int a = 10;
	int* p = &a;
	free(p);//ok?
}
3.4 使用free释放一块动态开辟内存的一部分
#include<stdio.h>
#include<stdlib.h>
int main()
{
	int* p = (int*)malloc(10 * sizeof(int));
	if (p == NULL)
		return 0;
	for (int i = 0; i < 10; i++)
	{
		*p = 1;
		p++;//改变了起始地址
	}
	free(p);
	p = NULL;
	return 0;
}
3.5 对同一块动态内存多次释放
#include<stdio.h>
#include<stdlib.h>
int main()
{
	int* p = (int*)malloc(10 * sizeof(int));
	if (p == NULL)
		return 0;
	free(p);//c第一次
	p = NULL;
	//一堆代码...
	free(p);//第二次
	p = NULL;
	return 0;
}
3.6 动态开辟内存未释放(内存泄漏)
#include<stdio.h>
#include<stdlib.h>
int main()
{
	int* p = (int*)malloc(100);
	if (NULL != p)
	{
		*p = 20;
	}
	return 0;
}
4. 笔试题
4.1 题目1
//请问运行Test 函数会有什么样的结果?
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
void GetMemory(char* p)
{
	p = (char*)malloc(100);
}
void Test(void)
{
	char* str = NULL;
	GetMemory(str);
	strcpy(str, "hello world");
	printf(str);
}
int main()
{
	Test();
	return 0;
}
解读
改进
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
void GetMemory(char** p)
{
	*p = (char*)malloc(100);
}
void Test(void)
{
	char* str = NULL;
	GetMemory(&str);
	strcpy(str, "hello world");
	printf(str);
	free(ptr);//free掉
	ptr = NULL;
}
int main()
{
	Test();
	return 0;
}
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
char* GetMemory(char* p)//改变了返回值类型
{
	p = (char*)malloc(100);
	return p;
}
void Test(void)
{
	char* str = NULL;
	str = GetMemory(str);//将返回值赋值给str
	strcpy(str, "hello world");
	printf(str);
	free(ptr);
	ptr = NULL;
}
int main()
{
	Test();
	return 0;
}
请思考 :一般变量在函数调用完毕后会被销毁,那这块开辟的内存空间也会被销毁吗?
4.2 题目2
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
char* GetMemory(void)
{
	char p[] = "hello world";
	return p;
}
void Test(void)
{
	char* str = NULL;
	str = GetMemory();
	printf(str);
}
int main()
{
	Test();
	return 0;
}
解读
4.3 题目3
#include<stdio.h>
#include<stdlib.>
#include<string.h>
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;
}
解读
4.4 题目4
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
void Test(void)
{
	char* str = (char*)malloc(100);
	strcpy(str, "hello");
	free(str);
	if (str != NULL)
	{
		strcpy(str, "world");
		printf(str);
	}
}
int main()
{
	Test();
	return 0;
}
解读
改进
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
void Test(void)
{
	char* str = (char*)malloc(100);
	strcpy(str, "hello");
	free(str);
	str = NULL;
	if (str != NULL)
	{
		strcpy(str, "world");
		printf(str);
	}
}
int main()
{
	Test();
	return 0;
}
5. C/C++程序的内存开辟
以一段代码为例

C/C++程序内存分配的几个区域:
以上图理解static修饰局部变量
6. 柔性数组
C99 中,结构中的最后一个元素允许是未知大小的数组,这就叫做柔性数组成员。
例如
typedef struct st_type
{
int i;
int a[0];//柔性数组成员
}type_a;
有些编译器会报错无法编译可以改成:
typedef struct st_type
{
int i;
int a[];//柔性数组成员
}type_a;
6.1 柔性数组的特点
typedef struct st_type
{
	int i;
	int a[0];//柔性数组成员
}type_a;
int main()
{
	printf("%d\n", sizeof(type_a));//输出的是4
	return 0;
}
6.2 柔性数组的使用
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
struct s
{
	int i;
	int a[0];//柔性数组成员
};
int main()
{
	int i = 0;
	struct s* p = (struct s*)malloc(sizeof(struct s) + 100 * sizeof(int));
	//使用指针变量维护和使用malloc开辟内存要强转
	p->i = 100;//修改成员i的值
	for (i = 0; i < 100; i++)
	{
		p->a[i] = i;//修改成员a的值
	}
	free(p);
	p = NULL;
	return 0;
}
6.3 柔性数组的优势
以上代码和下面等价,但有所不同。以下面的代码为例与之作比较。
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
typedef struct st_type
{
	int i;
	int* p_a;//后面把这个指针变量当数组使用
}type_a;
int main()
{
	type_a* p = (type_a*)malloc(sizeof(type_a));
	//为结构体开辟内存
	p->i = 100;//修改成员i
	p->p_a = (int*)malloc(p->i * sizeof(int));
	//为数组成员开辟内存
	for (int i = 0; i < 100; i++)
	{
		p->p_a[i] = i;//修改数组成员
	}
	//释放空间
	free(p->p_a);
	p->p_a = NULL;
	free(p);
	p = NULL;
	return 0;
}
这里将一个数组作为结构成员,并单独为它开辟内存空间,每次通过结构访问成员使用它,这和柔性数组的特性是十分类似的。
不同或者说是前者的优点
结语
动态内存管理是数据结构的基础,一个比较长的链表需要不断地开辟和释放内存,但这么做的意义不仅在于更灵活地使用数组,弥补一般数组不能按需增加长度的缺点,更在于这么做能提高内存的使用效率,这是单纯一个数组无法做到的。
 欢迎指正!
 如果你有收获的话,不妨给作者一个鼓励吧~










