动态内存分配/管理

目录

1、为什么要有动态内存分配

2、动态内存函数介绍

1、malloc

2、free

3、calloc

​编辑

4、realloc

 3、动态内存常见的错误

4、动态内存开辟相关好题

5、c/c++程序内存开辟示意图


int a,  int arr[10]  是固定地向内存申请连续的一块空间,但不能变长或变短随时调整。

在我们之前写的静态版通讯录中,我们创建了一个peopleinfo类型的数组date[100]用来存放100个人的个人信息,但是当我们的人员信息较小时,100个结构体显得有些浪费,而当我们所需存放的信息超过100时又不够用,随之我们就得修改这个数字,但是学了动态内存管理之后,我们可以动态地分配内存空间变大或变小,从而有效利用空间。

1、为什么要有动态内存分配

int val = 20;//在栈空间上开辟四个字节
char arr[10] = {0};//在栈空间上开辟10个字节的连续空间
但是上述的开辟空间的方式有两个特点:
1. 空间开辟大小是固定的。
2. 数组在申明的时候,必须指定数组的长度,它所需要的内存在编译时分配。
但是对于空间的需求,不仅仅是上述的情况。有时候我们需要的空间大小在程序运行的时候才能知道,
那数组的编译时开辟空间的方式就不能满足了。
这时候就只能使用动态内存开辟了。

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

2、动态内存函数介绍

1、malloc

#include

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

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

先在堆区上动态申请一定空间,使用后应该还给操作系统,如果不主动还,程序结束后会自动还,但是如果程序一直不结束,就一直“占着不用”,就会造成空间的浪费。

2、free

用来释放、归还申请的内存

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

int main()
{
	//申请40个字节,用来存放10个整型
	int* p = (int*)malloc(40);
	if (p == NULL)
	{
		printf("%s\n", strerror(errno));
		return 1;
	}
	//存放  1--10
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		*(p + i) = i + 1;
	}
	for (i = 0; i < 10; i++)
	{
		printf("%d ", *(p+i));
	}
	//free释放申请的内存
	free(p);

	return 0;
}

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

 用malloc申请后,内存中为随机值,使用时可给它们赋值,当free后,又变为随机值。

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

 仔细观察,我们可以发现,虽然这块空间的值发生了变化,但是指针p指向的地址free前后没有变化。因此,当我们free还给操作系统后,p仍指向这块空间。此时*p就会导致非法访问,因此我们需要将p制为NULL,避免非法访问。

	free(p);
	p = NULL;

总结:malloc申请空间后不会初始化,使用前要判断是否成功申请(是否返回NULL),使用后要free还给操作系统,然后将用于接收这块内存空间的指针p置为NULL。

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

 申请失败时返回NULL,并打印错误原因(没有足够空间)。

void  free(void * ptr)

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

3、calloc

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

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

由上图我们可以知道,malloc不会初始化,calloc会将每个元素先初始化为0,可以按需用。由于calloc需要初始化,效率比malloc稍低,此外没有其它区别,都需要进行相关步骤。

4、realloc

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

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

int main()
{
	int* p = (int*)malloc(5 * sizeof(int));
	if (NULL == p)
	{
		perror("malloc");
		return 1;
	}//使用
	int i = 0;
	for (i = 0; i < 5; i++)
	{
		p[i] = 1;
	}//空间不够,增加5个整型的空间
	//此时不能用p接收
	int* ptr = (int*)realloc(p, 10 * sizeof(int));
	//先用ptr接收,再赋给p,防止返回NULL,p找不到原来的数据
	if (ptr != NULL)
	{
		p = ptr;
        ptr = NULL;//释放但不置空,需手动置空
	}
	//继续使用
	for (i = 0; i < 10; i++)
	{
		printf("%d ", p[i]);
	}//realloc会把旧的空间释放,不用自己释放
	free(p);
	p = NULL;
}

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

 3、动态内存常见的错误

1、对NULL解引用(未判断是否为空)

2、非法访问内存,越界访问(解引用野指针)

3、对非动态开辟内存的空间用free释放

void test()
{
int a = 10;
int *p = &a;
free(p);//ok?
}

对栈区的空间进行释放(x)

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

void test()
{
int *p = (int *)malloc(100);
p++;
free(p);//p不再指向动态内存的起始位置
p=NULL;
}

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

void test()
{
int *p = (int *)malloc(100);
free(p);
free(p);//重复释放
}

free一个NULL空指针时,什么事都不会发生。

malloc等函数是申请一块空间,获得使用它的权限,free释放后,将权限还给操作系统,如果再访问或是释放,就是非法访问。
6、动态开辟的内存忘记释放(内存泄漏)

malloc和free要成对出现,防止出现内存泄漏

int* test()
//函数内部进行了malloc操作,返回了malloc开辟的空间的起始地址
//谁接收了  要记得释放和置空
{
	int* p = (int*)malloc(100);
	if (NULL == p)
	{
		return 1;
	}
	return p;
}
int main()
{
	int* ptr = test();
	free(ptr);
	ptr = NULL;
}

4、动态内存开辟相关好题

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

这里GetMemory函数是值传递,且没有返回值,对str无影响。因此str还是指向NULL,即0地址处,然后调用strcpy需访问0地址处内容,导致非法访问。

同时,上面用malloc申请了一块空间,但是函数内没有释放,函数销毁后,p也销毁,这块空间就会内存泄漏。记得malloc要与free搭配使用。

修改后,可传入&str,用char**p接收,*P=(char*)malloc(100),使p指向开辟的100byte,进而存放拷贝的内容,打印后free(str) str=NULL

或者将p(char*)直接返回,用str接收,因为没有free,所以malloc(100)仍然存在,进而可以strcpy,如果此时不是malloc申请,而是利用数组,函数销毁后,数组的空间也会释放,如果再进行打印就不行了。(如下图)这类问题简称为 返回栈空间地址的问题

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

可以在p前面加上static使其变为静态区变量,函数销毁后它不会销毁,或者去掉[],p变为char*类型,即从数组变为常量字符串,常量字符串也是在静态区,也就是把在栈区存储的数据放入静态区中存储,从而避免了返回栈空间地址的问题。

 printf(str)括号内直接加str是可以的,str是一个地址,例如printf(“hehe”),括号内有引号+字符串,也就是首字符的地址,相当于括号内直接加一个地址,最终结果都是打印字符串。

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

这段代码的整体思路没有问题,但是会导致内存泄漏,  malloc等函数需要与free共同使用

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

free(str)后已经没有权限访问了,但后面又调用strcpy访问空间,导致非法访问

开辟--释放--置空

习题来自《高质量C/C++编程》

5、c/c++程序内存开辟示意图

动态内存分配/管理_第13张图片

 1. 栈区(stack):在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结
束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是
分配的内存容量有限。 栈区主要存放运行函数而分配的局部变量、函数参数、返回数据、返
回地址等。
2. 堆区(heap):一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。分
配方式类似于链表。
3. 数据段(静态区)(static)存放全局变量、静态数据。程序结束后由系统释放。
4. 代码段:存放函数体(类成员函数和全局函数)的二进制代码,只读常量(不能被修改),字符串常量。

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