动态内存管理相关知识点以及易错题

动态内存分配的变量都是在堆区上面建立的。

目录

1、动态内存函数的介绍

1.1 malloc和free

1.2 calloc

1.3 realloc

2.常见的动态内存错误

2.1 对NULL指针的解引用操作

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

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

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

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

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

3.几个经典的笔试题

3.1  笔试题一

3.2  笔试题二

3.3  笔试题三

3.3  笔试题四



1、动态内存函数的介绍

动态内存函数的头文件均为 #include<stdlib.h>

1.1 malloc和free

malloc函数原型:

void* malloc(size_t size); //返回的是void*指针,不是无返回值

free函数原型:

void free(void*ptr); //无返回值
  • 关于malloc函数:
  1. 如果开辟成功,则返回一个开辟好空间的指针(返回起始地址)
  2. 如果开辟失败,则返回一个NULL指针,因此malloc的返回值一定要检查(必须保证在malloc返回值为非NULL才能继续后面的操作)
  3. 返回值为void*,因此malloc函数并不知道开辟空间的类型,我们要将返回值强制转换为我们需要的类型
  4. 如果参数size为0,malloc的行为是ANSI C未定义的,取决于编译器
  • 关于free函数:
  1. 如果参数ptr指向的空间不是动态开辟,那free函数的行为是未定义的
  2. 如果参数ptr是NULL指针,则函数什么事都不做

注意:

  • 如果我们用ptr去接收malloc的返回值,例如:struct s ptr=(struct s*)malloc(10*sizeof(int)),我们最好不要改变ptr的值,始终让ptr指向动态内存开辟的起始地址
  • free掉了malloc开辟的那块空间,此时已经把那块空间的使用权归还给操作系统了,不再属于我们,但ptr仍指向那块空间的起始地址,此时ptr属于野指针,那么我们应该把ptr置为NULL

举个栗子

#include<stdio.h>
#include<stdlib.h>
int main()
{
	int *p = (int*)malloc(sizeof(int) * 10);
	if (p == NULL)
	{
		perror("malloc:");
		return;
	}
	for (int i = 0; i < 10; i++)
	{
		p[i] = i;
		printf("%d ", p[i]);
	}
	free(p);
	p = NULL;
	return 0;
}

如果不释放动态开辟的内存会怎么样?

当我们不释放动态开辟的内存时,如果程序结束,动态内存由操作系统自动回收,但是如果程序不释放,动态内存是不会自动回收,就会造成内存泄漏的问题。

1.2 calloc

calloc函数的原型:

void * calloc (size_t num , size_t  size) ;
  • 关于calloc函数:

       函数的功能是为num个大小为size的元素开辟一块空间,并且把每个字节都初始化为0

  • malloc和calloc的联系与区别
  1. 联系:malloc和calloc都是动态开辟内存空间,返回的都是空间的起始地址。
  2. 区别:malloc只有size一个参数,且size是总字节数;calloc有两个参数,num是元素个数,size是每一个元素所占字节的大小,且每个字节初始化为0。

 举个栗子

#include<stdio.h>
#include<stdlib.h>
int main()
{
	int *p = (int*)calloc(10,sizeof(int));
	if (p == NULL)
	{
		perror("calloc:");
		return;
	}
	for (int i = 0; i < 10; i++)
	{
		p[i] = i;
		printf("%d ", p[i]);
	}
	free(p);
	p = NULL;
	return 0;
}

 

 (博主说一句话:到目前为止,我感觉我用malloc还是用的多)

1.3 realloc

都说动态内存开辟空间灵活,怎么个灵活法呢?我们首先用malloc或者calloc申请一块固定大小的空间,后面发现这块空间不够我们去挥霍,那怎么办呢?我们此时要用realloc函数去扩大这块空间。

realloc函数的原型:

void * realloc(void * ptr,size_t size);
  • 注意:
  1. ptr是要调整的内存地址。这个ptr也就是接受malloc函数返回值(动态开辟内存的空间的起始地址)的指针。
  2. size是调整之后空间的新大小。
  3. 返回值为调整之后的内存起始地址。
  4. 这个函数调整原内存空间大小的基础上,还会将原来内存中的数据移动到新的空间中。

下面代码有注释这一行是在使用realloc的过程中易错的一点:

#include<stdio.h>
#include<stdlib.h>
int main()
{
	int *p = (int*)malloc(10*sizeof(int));
	if (p == NULL)
	{
		perror("calloc:");
		return;
	}
	}
	p = (int *)realloc(p, 20 * sizeof(int));   //这样可以吗?这样可以吗?这样可以吗?
	free(p);
	p = NULL;
	return 0;
}

显然是不可以这样写的,如果realloc未成功(比如找不到额外空间来扩容),返回的是NULL指针,这样的话,既没有达到扩容的目的,p指针也会丢掉原来空间的地址,而变成野指针,因此不能这样做。

正确做法:临时创建一个指针变量来接受realloc返回的地址,在判断该地址不为null则可以将该地址赋值给p指针。具体代码如下:

#include<stdio.h>
#include<stdlib.h>
int main()
{
	int *p = (int*)malloc(10*sizeof(int));
	if (p == NULL)
	{
		perror("calloc:");
		return;
	}
	int *ptr = realloc(p, 20 * sizeof(int));  //正确做法
	if (ptr == NULL)
	{
		perror("realloc:");
		return;
	}
	free(p);
	p = NULL;
	ptr = NULL;
	return 0;
}

2.常见的动态内存错误

2.1 对NULL指针的解引用操作

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

在未判断p是否为NULL时,对其解引用是有很大问题的

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

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

void test()
{
	int i = 0;
	int *p = (int *)malloc(10*sizeof(int));
	if (p == NULL)
	{
		return;
	}
	for (i = 0; i <= 10; i++)
	{
		*(p + i) = i;   //当i是10的时候越界访问
	}
	free(p);
}

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

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

上文也有提到过,如果对非动态开辟内存使用free释放,这个行为ANSI C中是未定义的。这个空间是由编译器来释放的,不需要我们来释放。

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

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

p此时没有指向动态开辟空间的起始地址,不能释放其中一部分的空间,一定要记住起始地址,不然这个空间就释放不了了

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

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

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

void test()
{
	int i = 0;
	int *p = (int *)malloc(10*sizeof(int));
	if (p == NULL)
	{
		return;
	}
	for (i = 0; i <= 10; i++)
	{
		*(p + i) = i;   //当i是10的时候越界访问
	}
	free(p);
}

3.几个经典的笔试题

3.1 笔试题一

void GetMemory(char *p)
{
	p = (char*)malloc(100);
}
void Test(void)
{
	char *str = NULL;
	Getmemory(str);
	strcpy(str, "hello world");
	printf(str);
}

错因: 

void GetMemory(char *p)
{
	p = (char*)malloc(100);   //没有free(p),会造成内存泄漏
}
void Test(void)
{
	char *str = NULL;
	Getmemory(str);  //str的值没有被改变,str仍为NULL
	strcpy(str, "hello world");  //对NULL指针进行解引用操作,程序崩溃
	printf(str);  
}

可能有人会对这两步操作有疑问

strcpy(str,"hello world");
printf(str);

函数strcpy的原型如下:

char * strcpy ( char * destination, const char * source );

strcpy ( str , " hello world ") ; 这一步实际上是这样的:

char a[] = "hello world";
for (int i = 0; i < sizeof("hello world"); i++)
{
	*(str + i) = a[i];
}

i=0时,实际上是在对NULL指针的解引用操作,且i>0后,str+i这一块空间不知道是否属于我们

printf(str);这一步是以为str得到了动态开辟的内存,且实现了上面这个strcpy的代码,然后要从str这个地址开始向后打印字符。

char *str="hello world";  //把“hello world”中‘h’的地址传给str
printf(str);//==printf("hello world")  ==printf("%s",str)

通过这个例子,我们可以知道,如果有一个字符数组str[num],printf("%s",str)与printf(str)的效果一样

这题应该改成这样:

void GetMemory(char **p)
{
	*p = (char*)malloc(100);   //没有free(p),会造成内存泄漏
}
void Test(void)
{
	char *str = NULL;
	Getmemory(&str);  //str的值被改变
	strcpy(str, "hello world");  
    printf(str);
    free(str);
    str=NULL;  
}

3.2  笔试题二

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

在GetMemory函数中,p接受到了“hello world”中首字母也就是'h'的地址,此时这块地址是属于我 们的。

但出了GetMemory函数后,返回给main函数的值即‘h’的地址就不属于我们了,str现在指向一块不属于我们的地址,故str为野指针。printf(str)会打印出我们意料之外的东西。

3.3  笔试题三

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

这题也没有什么很明显的错误,就是有个地方需要注意一点:最好判断malloc(num)返回的是不是空指针

3.3  笔试题四

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

free完str后没有把str置为NULL,str中始终存着动态内存开辟的那块空间,肯定不为NULL,就会把“world”拷贝到str指向的空间,而这块空间缺不属于我们,属于非法访问。

文章到这里就结束啦,觉得这篇文章写的还不错的朋友可以点个赞/关注再走,谢谢!

  • 5
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值