动态内存分配

本文详细介绍了C/C++中的动态内存分配函数,如malloc、calloc和realloc的使用方法及注意事项,同时也列举了常见的动态内存错误,如空指针解引用、越界访问和错误释放。此外,讨论了动态数组和柔性数组的概念及其应用。
摘要由CSDN通过智能技术生成

目录

动态内存分配函数

malloc和free

calloc

realoc

常见的动态内存错误

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

动态数组

长度可变的一维动态数组

长度可变的二维动态数组

柔性数组


堆区:动态内存分配

动态内存分配函数

使用动态内存函数,需要包含头文件<stdlib.h>

malloc和free

void* malloc(size_t size)

向内存申请一块连续可用的空间,并返回一个指向开辟好空间的指针,如果没用足够的空间,即申请失败,会返回NULL,所以malloc的返回值一定要做检查!!!
返回值得类型是void*,所以malloc函数并不知道开辟空间的类型,具体要在使用的时候使用着自己来决定
如果size为0,malloc的行为是标准是未定义的,取决于编译器

void free(void* memblock)

释放动态内存空间
如果参数指向的空间不是动态开辟的,那么free函数的行为是未定义的
如果参数是NULL,则函数什么事都不做

int main()
{
	//向内存申请10个整型的空间
	int* p = (int*)malloc(10 * sizeof(int));
	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设置为空指针
	p = NULL;
	return 0;
}

calloc

void* calloc(size_t num, size_t size)

开辟一个空间,并且把元素初始化为0,num是多少个元素size是一个元素几个字节,注意和malloc的区别
开辟失败也会返回空指针

int main()
{
	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(p);
	p = NULL;
	return 0;
}

realoc

void* realloc(void* ptr, size_t size)

 用于对已经开辟的动态内存大小进行调整,ptr是要调整的内存地址,size是调整之后新大小,返回值为调整之后内存的起始位置
 这个函数调整原内存空间大小的基础上,还会将原来内存中的数据移动到新的空间
 relloc在调整内存空间存在两种情况:1.原有空间之后有足够大的空间 2.原有空间之后没有足够大的空间↓
注意事项
 如果p指向的空间之后有足够的的内存空间可以追加,则直接追加,返回旧地址
 如果不够追加,有可能会导致非法访问,所以会重新找一个新的内存区域,开辟一个满足需求的空间,将原来内存中的数据拷贝回来,释放旧的内存空间,并返回新地址
 要用一个新的变量来接收relloc函数返回值,因为如果追加失败会返回NULL,还用原来的变量接收的话,不仅追加失败,还会丢失p所指向的空间,应当建立新的变量后进行判断不为NULL后再赋值给原变量
 释放的时候注意要将两个指针变量都变为NULL

常见的动态内存错误

对null指针的解引用操作(必须要对返回值进行判断)

void test()
{
	int* p = (int*)malloc(INT_MAX / 4);
	*p = 20;
	free(p);
}

对动态开辟地址的越界访问 

void test()
{
	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;
}

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

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

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

void test()
{
	int* p = (int*)malloc(40);
	if (p = NULL)
	{
		return 0;
	}
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		*p++ = i;//注意p指向的地址改变了
	}
	free(p);//并且这时p指向了开辟的内存空间外部
	p = NULL;
}

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

void test()
{
	int* p = (int*)malloc(10);
	free(p);
	free(p);//但是如果在第一次释放后再把p赋值为空指针,那么之后的多次释放p也是无效的,并不会发生错误
}

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

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

动态开辟的空间一定要释放,并且正确释放

错误辨析 

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

程序会崩溃,原因是Test传递的是str的值,并不是str的地址,所以在函数中更改p的指向并不会影响str,并且函数执行完成后,p就会被释放,但开辟的空间并没有释放且无法找到,并且str仍是空指针

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

p在栈区,执行完成后数组就被释放了(堆区不free就会已知存在),str变成了野指针,再进行打印就会非法访问

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

主要分为6大部分
 1.内核空间(用户代码不能读写)
 2.栈(向下增长)
 3.内存映射段(文件映射,动态库,匿名映射)
 4.堆(向上增长)
 5.数据段/静态区(全局数据,静态数据)
 6.代码段(可执行代码/只读常量)
其中,栈区:在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放,栈内存分配运算内置于处理器的指令集中,效率很高,但分配的内存器容量有限。栈区主要存放运行函数而分配的局部变量,函数参数,返回数据,返回地址等
堆区:一般由程序员分配释放,若程序员不释放,程序运行时可能由操作系统(OS)回收,分配方式类似于链表
数据段(静态区):存放全局变量,静态数据,程序结束后由系统释放
代码段:存放函数体(类成员函数和全局函数)的二进制代码

动态数组

长度可变的一维动态数组

编程输入某班学生的某门课成绩,计算并输出其平均分,学生人数由键盘输入

void inputscore(int* p, int n)
{
	int i;
	for (i = 0; i < n; i++)
	{
		scanf("%d", &p[i]);
	}
}
double average(int* p, int n)
{
	int i, sum = 0;
	for (i = 0; i < n; i++)
	{
		sum = sum + p[i];
	}
	return (double)sum / n;
}
int main()
{
	int* p = NULL, n;
	double aver;
	printf("请输入学生数量:");
	scanf("%d", &n);
	p = (int*)malloc(n * sizeof(int));
	if (p == NULL)
	{
		printf("空间不足\n");
		exit(1);
	}
	printf("请输入成绩:\n");
	inputscore(p, n);
	aver = average(p, n);
	printf("平均分为%.1f\n", aver);
	free(p);
	p = NULL;
	return 0;
}

向系统申请n个int型的存储单元,用int型指针变量p指向了这段连续的存储空间的地址。这就相当于建立了一个动态一维数组,可通过首地址p来寻址 数组中的元素,即可以使用*(p+i)或p[i]来表述数组元素值

长度可变的二维动态数组

编程输入m个班的学生(每个班n个学生)的某门课成绩,计算并输入平均分。班级数和每班学生数由键盘输入

void inputscore(int* p, int m, int n)
{
	int i, j;
	for (i = 0; i < m; i++)
	{
		printf("请输入第%d班的成绩:\n", i + 1);
		for (j = 0; j < n; j++)
		{
			scanf("%d", &p[i * n + j]);
		}
	}
}
double average(int* p, int m, int n)
{
	int i, j, sum=0;
	for (i = 0; i < m; i++)
	{
		for (j = 0; j < n; j++)
		{
			sum = sum + p[i * n + j];
		}
	}
	return (double)sum / (m * n);
}
int main()
{
	int* p = NULL, n, m;
	double aver;
	printf("请输入班级数量:");
	scanf("%d", &m);
	printf("请输入学生数量:");
	scanf("%d", &n);
	p = (int*)calloc(m * n,  sizeof(int));
	if (p == NULL)
	{
		printf("空间不足\n");
		exit(1);
	}
	inputscore(p, m, n);
	aver = average(p, m, n);
	printf("平均分为%.1f\n", aver);
	free(p);
	p = NULL;
	return 0;
}

向系统申请m*n个int型的存储单元,并用int型指针变量p指向这段内存的首地址。尽管它相当于建立了一个m行n列的二维动态数组,但因指针变量p是指向这个二维动态数组的列指针,所以通过指针p来寻址数组元素时,必须将其当做一个一维数组来处理,只能使用*(p+i*n+j)或者p[i*n+j]来表示数组元素值 

柔性数组

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

struct S
{
	int n;
	int arr[];//未知大小的,也可以写成arr[0],数组的大小是可以调整的
};
int main()
{
	struct S s;
	//printf("%d\n", sizeof(s));//4,没有包含数组大小
	struct S* ps = (struct S*)malloc(sizeof(struct S) + 5 * sizeof(int));//开辟一个结构S大小外加5个整型大小的空间
	//如此开辟的空间有24个字节,前4个给结构体中的n,剩下的20个就是给arr的
	ps->n = 100;
	int i = 0;
	for (i = 0; i < 5; i++)
	{
		ps->arr[i] = i;
	}
	//进行空间调整 
	struct S* ptr = realloc(ps, 44);
	if (ptr != NULL)
	{
		ps = ptr;
	}
	for (i = 5; i < 10; i++)
	{
		ps->arr[i] = i;
	}
	for (i = 0; i < 10; i++)
	{
		printf("%d ", ps->arr[i]);//不能用s.arr[i]
	} 
	free(ptr);
	ps = NULL;
	ptr = NULL;
	return 0;
}

方法2(非柔性数组)

struct S
{
	int n;
	int* arr;
};
int main()
{
	struct S* ps = (struct S*)malloc(sizeof(struct S));//开辟了一个空间,能容纳n和指针arr
	ps->arr = (int*)malloc(5 * sizeof(int));
	int i = 0;
	for (i = 0; i < 5; i++)
	{
		ps->arr[i] = i;
	}
	//调整大小
	int* ptr = (int*)realloc(ps->arr, 10 * sizeof(int));
	if (ptr)
	{
		ps->arr = ptr;
	}
	for (i = 5; i < 10; i++)
	{
		ps->arr[i] = i;
	}
	for (i = 0; i < 10; i++)
	{
		printf("%d ", ps->arr[i]);
	}
	//释放内存,先释放arr,再释放ps,如果先释放ps的话,就找不到arr开辟的空间了
	free(ps->arr);
	ps->arr = NULL;
	free(ps);
	ptr = NULL;
	ps = NULL;
	return 0;
}

 缺点:需要多次malloc,多次free,创建动态数组的时候是哪里有空间就在哪里创建,这样创建会产生许多不大不小的内存碎片,导致空间利用率低,并且创建的空间不连续,访问效率低

也可以只让数组中的元素,那么可以不在堆区中开辟结构的空间,直接讲结构体的地址传给ps也可以,要注意的是后面就不要释放ps了

柔性数组的特点

 结构中的柔性数组成员前必须至少一个其他成员
 sizeof返回的这种结构大小不包括柔性数组的内存
 包含柔性数组成员的结构用malloc函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小

柔性数组的优点

 方便内存释放
 有利于访问速度

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值