C【动态内存管理】

1. 为什么存在动态内存分配

cs 复制代码
int val = 20;//在栈空间上开辟四个字节
char arr[10] = {0};//在栈空间上开辟10个字节的连续空间

2. 动态内存函数的介绍

2.1 malloc:stdlib.h

cpp 复制代码
void* malloc (size_t size);
int* p = (int*)malloc(40);
cpp 复制代码
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <stdio.h>

int main()
{
	//向内存申请10个整形的空间
	int* p = (int*)malloc(40);
	if (p == NULL)
	{
		//打印错误原因的一个方式
		printf("%s\n", strerror(errno));
	}
	else
	{
		//正常使用空间
		int i = 0;
		for (i = 0; i < 10; i++)
		{
			*(p + i) = i;
		}
		for (i = 0; i < 10; i++)
		{
			printf("%d ", *(p + i));
		}
	}
	//当动态申请的空间不再使用的时候
	//就应该还给操作系统
	free(p);
    //上面是将p断开,但是实际上p还是存储内容,所以我们手动设置为null
	p = NULL;

	return 0;
}

2.2 free:stdlib.h

是用来做动态内存的释放和回收的

注意点:free(str)后,实际上str还执行一个空的地址,所以此时str!=NULL

cpp 复制代码
void free (void* ptr);

2.3 calloc

calloc 函数也用来动态内存分配。可以初始化空间。

cs 复制代码
void* calloc (size_t num, size_t size);
int*p = (int*)calloc(10, sizeof(int));
cpp 复制代码
int main()
{
	//malloc(10*sizeof(int))
	int*p = (int*)calloc(10, sizeof(int));

	if (p == NULL)
	{
		printf("%s\n", strerror(errno));
	}
	else
	{
		int i = 0;
		for (i = 0; i < 10; i++)
		{
			printf("%d ", *(p + i));
		}
	}
	//释放空间
	//free函数是用来释放动态开辟的空间的
	free(p);
	p = NULL;

	return 0;
}

2.4 realloc

当初始申请空间不够,这时使用realloc开辟新的空间【调整动态开辟内存空间的大小】

使用注意点:

  1. 如果p指向的空间之后有足够的内存空间可以追加,则直接追加,后返回p

  2. 如果p指向的空间之后没有足够的内存空间可以追加,则realloc函数会重新找一个新的内存区域

开辟一块满足需求的空间,并且把原来内存中的数据拷贝回来,释放旧的内存空间

最后返回新开辟的内存空间地址

  1. 得用一个新的变量来接受realloc函数的返回值
cpp 复制代码
void* realloc (void* ptr, size_t size);
cpp 复制代码
#include <stdio.h>
int main()
{
 int *ptr = (int*)malloc(100);
 if(ptr != NULL)
 {
     //业务处理
 }
 else
 {
     exit(EXIT_FAILURE);    
 }
 //扩展容量
 //代码1
 ptr = (int*)realloc(ptr, 1000);//这样可以吗?(如果申请失败会如何?)

 //代码2
 int*p = NULL;
 p = realloc(ptr, 1000);
 if(p != NULL)
 {
 ptr = p;
 }
 //业务处理
 free(ptr);
 return 0;
}

3. 常见的动态内存错误

3.1 对NULL指针的解引用操作

cpp 复制代码
//1. 对NULL进行解引用操作
	int *p = (int*)malloc(40);
	//万一malloc失败了,p就被赋值为NULL
	//所以我们在申请完一块空间之后,一定要进行判空操作
	*p = 0;//err

	int i = 0;
	for (i = 0; i < 10; i++)
	{
		*(p + i) = i;//err
	}
	free(p);
	p = NULL;

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

cpp 复制代码
//2. 对动态开辟的内存的越界访问
	int *p = (int*)malloc(5 * sizeof(int));
	if (p == NULL)
	{
		return 0;
	}
	else
	{
		int i = 0;
		for (i = 0; i < 10; i++)
		{
			*(p + i) = i;
		}
	}
	//
	free(p);
	p = NULL;

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

cpp 复制代码
//栈区开辟出来的,不是动态开辟
	int a = 10;
	int* p = &a;
	*p = 20;
	//3. 对非动态开辟内存的free
	free(p);
	p = NULL;

	return 0;

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

cpp 复制代码
int*p = (int*)malloc(40);
	if (p == NULL)
	{
		return 0;
	}
	int i = 0;
	for (i = 0; i < 5; i++)
	{
		*p++ = i;
	}
	//回收空间
	// 使用free释放动态开辟内存的一部分
	free(p);
	p =NULL;

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

cpp 复制代码
int *p = (int*)malloc(40);
	if (p == NULL)
	{
		return 0;
	}
	//使用
	//释放
	free(p);
	//将p设置为空指针,可以防止重复释放产生的错误
	p = NULL;

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

cpp 复制代码
while (1)
	{
		malloc(1);
	}

4. 几个经典的笔试题

4.1 题目1:

cpp 复制代码
//面试1:
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();

	char*str = "abcdef";
	printf("%s\n", str);
	printf(str);
	printf("abcdef");

	return 0;
}

修改结果

cpp 复制代码
void GetMemory(char **p)//**p:是p的地址
{
	//*p:是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;
}
cpp 复制代码
char* GetMemory(char *p)
{
	p = (char *)malloc(100);
	//将p传递除去
	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;
}

4.2 题目2:

​​​​​​​

【存储在栈区中的数据,出了函数则就会被销毁】

cpp 复制代码
//面试2
char *GetMemory(void)
{
	char p[] = "hello world";//局部变量
	//跳出此函数,则p被销毁
	return p;
}

void Test(void)
{
	char *str = NULL;
	str = GetMemory();//此时str的p的地址
	printf(str);//非法地址访问,故输出随机值
}

int main()
{
	Test();
	return 0;
}

【数据存储在静态区(static),出了函数数据还是存在】

cpp 复制代码
//在静态区,出了函数还是可以继续使用
int* test()
{
	//使用static,将a放入静态区,出了这个函数,内存并没有被销毁,故在外面还可以访问到
	static int a = 10;//静态区
	int a = 10;//栈区 
	return &a;
}

int main()
{
	int*p = test();//此时p接收到a的地址
	*p = 20;//将a修改为20
	return 0;//20
}

【数据存储在堆区,出了函数数据还是存在】

cpp 复制代码
//在堆区,出函数还是存在
int* test()
{
	int *ptr = malloc(100);//堆区
	return ptr;
}

int main()
{
	int *p = test();

	return 0;
}

4.3 题目3:

cpp 复制代码
void GetMemory(char **p, int num)
{
	*p = (char *)malloc(num);//给p创建100个新的char
}
void Test(void)
{
	char *str = NULL;
	GetMemory(&str, 100);
	strcpy(str, "hello");//可以输出
	printf(str);
	//改:忘记free内容,导致内存泄露
	free(str);
	str = NULL;
}

4.4 题目4:

cpp 复制代码
void Test(void)
{
	char *str = (char *)malloc(100);
	strcpy(str, "hello");
	free(str);
	//free了但是没有把指针置为null
	//此处的问题:已经释放的空间,还被使用
	if (str != NULL)//则此时str还不为NUll,则进入判断
	{
		strcpy(str, "world");
		printf(str);
	}
}

int main()
{
	Test();//world

	return 0;
}

解决:

cs 复制代码
void Test(void)
{
	char* str = (char*)malloc(100);
	strcpy(str, "hello");
	free(str);
	//解决:将str置为NULL
	str = NULL;
	if (str != NULL)
	{
		strcpy(str, "world");
		printf(str);
	}
}

int main()
{
	Test();//world

	return 0;
}

5. C/C++程序的内存开辟

C/C++程序内存分配的几个区域:

  1. 栈区(stack):在执行函数时**,函数内局部变量的存储单元**都可以在栈上创建,函数执行结 束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是 分配的内存容量有限。 栈区主要存放运行函数而分配的局部变量、函数参数、返回数据、返 回地址等。

  2. 堆区(heap):一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。分 配方式类似于链表。

  3. 数据段(静态区)(static)存放全局变量、静态数据。程序结束后由系统释放。

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

6. 柔性数组

结构中的最后一个元素允许是未知大小的数组,这就叫做『柔性数组』成员。

cpp 复制代码
struct S
{
	int n;
	int arr[10];
};

struct S
{
	int n;
	int arr[];//未知大小的
};

struct S
{
	int n;
	int arr[0];//未知大小的-柔性数组成员-数组的大小是可以调整的
};

6.1 柔性数组的特点:

  • 结构中的柔性数组成员前面必须至少一个其他成员。
  • sizeof 返回的这种结构大小不包括柔性数组的内存。
  • 包含柔性数组成员的结构用malloc ()函数进行内存的动态分配,并且分配的内存应该大于结构的大 小,以适应柔性数组的预期大小。
cpp 复制代码
//代码1
int i = 0;
type_a *p = (type_a*)malloc(sizeof(type_a)+100*sizeof(int));
//业务处理
p->i = 100;
for(i=0; i<100; i++)
{
     p->a[i] = i;
}
free(p);

6.2 柔性数组的使用--int[] a

cpp 复制代码
struct S
{
	int n;
	int arr[0];//未知大小的-柔性数组成员-数组的大小是可以调整的
};

int main()
{
	struct S s;
	printf("%d\n", sizeof(s));//
	//sizeof(struct S):不包括int arr的大小
	//5*sizeof(int):手动的给arr赋值
	struct S* ps = (struct S*)malloc(sizeof(struct S)+5*sizeof(int));
	ps->n = 100;

	int i = 0;
	for (i = 0; i < 5; i++)
	{
		ps->arr[i] = i;//0 1 2 3 4
	}
	//开辟内存
	struct S* ptr = realloc(ps, 44);
	if (ptr != NULL)
	{
		ps = ptr;
	}
	for (i = 5; i < 10; i++)
	{
		ps->arr[i] = i;
	}
	//打印arr所有数值
	for (i = 0; i < 10; i++)
	{
		printf("%d ", ps->arr[i]);
	}

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

	return 0;
}

6.3 柔性数组的扩展:int* arr

cpp 复制代码
struct S
{
	int n;
	int* arr;
};
int main()
{
	//sizeof(struct S):此时包括int* arr
	struct S* ps = (struct S*)malloc(sizeof(struct S));
	//再一次给arr创建动态内存
	ps->arr = malloc(5 * sizeof(int));

	int i = 0;
	for (i = 0; i < 5; i++)
	{
		ps->arr[i] = i;
	}
	for (i = 0; i < 5; i++)
	{
		printf("%d ", ps->arr[i]);
	}
	//调整大小
	int* ptr = realloc(ps->arr, 10 * sizeof(int));
	if (ptr != NULL)
	{
		ps->arr = ptr;
	}
	for (i = 5; i < 10; i++)
	{
		ps->arr[i] = i;
	}
	for (i = 0; i < 10; i++)
	{
		printf("%d ", ps->arr[i]);
	}
	//释放内存:注意释放顺序
	free(ps->arr);
	ps->arr = NULL;
	free(ps);
	ps = NULL;

	return 0;
}

6.4 int arr[0] 和 int* arr的区别

上述 代码1 和 代码2 可以完成同样的功能,但是 方法1 的实现有两个好处:

​​​​​​​

相关推荐
im_AMBER1 分钟前
Leetcode 139 最后一个单词的长度 | 找出字符串中第一个匹配项的下标
开发语言·算法·leetcode
2401_889884669 分钟前
嵌入式C++测试框架
开发语言·c++·算法
1104.北光c°10 分钟前
我理解的Leaf号段模式:美团分布式ID生成系统
java·开发语言·笔记·分布式·github·leaf
DREW_Smile14 分钟前
字符函数和字符串函数2
c语言·开发语言
Z9fish23 分钟前
C语言算法专题总结(一)排序
c语言·算法·排序算法
wjs202425 分钟前
CSS 颜色
开发语言
Felven26 分钟前
B. Roof Construction
c语言
无巧不成书021827 分钟前
Java数值字面量速查表
java·开发语言·python·开发者·字面量
小鸡吃米…27 分钟前
测试线程应用程序
开发语言·python
python开发笔记28 分钟前
python(79) 底层代码追踪工具
开发语言·python