动态内存管理

目录

 

为什么存在动态内存分配

动态内存函数的介绍

malloc

free

calloc

realloc

常见的动态内存错误

几道经典的笔试题

柔性数组


为什么存在动态内存分配

我们现在已知开辟空间的方法有两种:

//创建变量

int x = 0;//在栈空间上开辟了四个字节的空间

//创建数组

char arr[10] = { 0 };//在栈空间上开辟10个字节的连续空间

这两种开辟空间的方法有两个特点:

1.空间开辟大小是固定的

2.数组在申请的时候,必须指定数组的长度,它所需要的内存在编译时分配。

但是对于空间的需求,不仅仅是上述的情况。有时候我们需要的空间大小在程序运行的时候才能知道,那数组的编译时开辟空间的方式就不能满足了。

这时就要用到动态内存开辟了。

动态内存函数的介绍

malloc

通过MSDN,我们先对malloc进行简单的了解

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

malloc返回值的翻译

 malloc returns a void pointer to the allocated space, or NULL if there is insufficient memory available. To return a pointer to a type other than void, use a type cast on the return value. The storage space pointed to by the return value is guaranteed to be suitably aligned for storage of any type of object. If size is 0, malloc allocates a zero-length item in the heap and returns a valid pointer to that item. Always check the return from malloc, even if the amount of memory requested is small.

malloc返回一个指向已分配空间的void指针,如果可用内存不足,则返回NULL。若要返回指向非void类型的指针,请对返回值进行类型转换。返回值所指向的存储空间保证对任何类型的对象进行适当的对齐。如果size为0,malloc会在堆中分配一个长度为0的项,并返回一个指向该项的有效指针。总是检查malloc的返回值,即使请求的内存量很小。

free

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

 注:free只能释放你通过动态内存函数开辟出来的空间,非动态开辟的内存空间是无法进行free。

这两个函数讲完,我们就可以写简单的代码:

#include 
#include 

int main()
{
	int i = 0;
	int* p = NULL;
	p = (int*)malloc(sizeof(int) * 10);
	for (i = 0; i < 10; i++)
	{
		*(p + i) = i;
	}
	for (i = 0; i < 10; i++)
	{
		printf("%d ", *(p + i));
	}
	free(p);
	p = NULL;//这里需要将p指向NULL是为了防止在p所指的40个字节的空间被释放后,再次使用仍指向这片空间的p时,会导致非法访问
	return 0;
}

calloc

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

 calloc返回值相关说明的翻译:

 calloc returns a pointer to the allocated space. The storage space pointed to by the return value is guaranteed to be suitably aligned for storage of any type of object. To get a pointer to a type other than void, use a type cast on the return value.

 返回一个指向已分配空间的指针。返回值所指向的存储空间保证对任何类型的对象进行适当的对齐。要获得一个指向非void类型的指针,请在返回值上使用类型转换。

#include 
#include 

int main()
{
	int i = 0;
	int* p = NULL;
	p = (int*)calloc(10,sizeof(int));
	for (i = 0; i < 10; i++)
	{
		printf("%d ", *(p + i));
	}
	free(p);
	p = NULL;
	return 0;
}

 realloc

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

 realloc返回值相关信息的翻译:

realloc returns a void pointer to the reallocated (and possibly moved) memory block. The return value is NULL if the size is zero and the buffer argument is not NULL, or if there is not enough available memory to expand the block to the given size. In the first case, the original block is freed. In the second, the original block is unchanged. The return value points to a storage space that is guaranteed to be suitably aligned for storage of any type of object. To get a pointer to a type other than void, use a type cast on the return value.

Realloc返回一个指向重新分配(可能移动)内存块的void指针。如果大小为0且缓冲区参数不是NULL,或者没有足够的可用内存来将块扩展到给定的大小,则返回值为NULL。在第一种情况下,原始块被释放。在第二种情况下,原始块没有改变。返回值指向一个存储空间,该存储空间保证对任何类型的对象进行适当的对齐。要获得一个指向非void类型的指针,请在返回值上使用类型转换。

#include 
#include 

int main()
{
	int i = 0;
	int* p = NULL;
	p = (int*)calloc(10,sizeof(int));
	for (i = 0; i < 10; i++)
	{
		printf("%d ", *(p + i));
	}
	p = (int*)realloc(p, 40);
	if (p == NULL)
	{
		return 0;//如果无法开辟空间,就会把p指向NULL,这样就要直接结束,否则就会造成非法访问
	}
	while (i < 20)
	{
		*(p + i) = i;
		i++;
	}
	for (i = 10; i < 20; i++)
	{
		printf("%d ", *(p + i));
	}
	free(p);
	p = NULL;
	return 0;
}

常见的动态内存错误

1.对NULL指针的解引用操作

#include 
#include 
#include 


int main()
{
	int* p=(int*)malloc(INT_MAX);

	//由于malloc需要申请的空间过大,无法申请,所以会返回一个空指针NULL,这时,p就是一个空指针
	//故为了解决这个问题就要在申请后进行判断
	if (p == NULL)
	{
		return 0;
	}
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		*(p + i) = i;//由于p是一个空指针,所以这里访问的空间是无法使用的
	}
	free(p);
	return 0;
}

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


#include 
#include 
#include 
#include 

int main()
{
	char* p = (char*)malloc(10 * sizeof(char));
	if (p == NULL)
	{
		printf("%s\n", strerror(errno));
		return 0;
	}
	//使用
	int i = 0;
	//for (i = 0; i <= 10; i++)//越界
	for (i = 0; i < 10; i++)//正确
	{
		*(p + i) = 'a' + i;
	}
	//释放
	free(p);
	p = NULL;
	return 0;
}

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


#include 
#include 

int main()
{
	//行
	int* p = (int*)malloc(40);
	......
	free(p);
	p = NULL;
	//这是通过动态内存开辟申请的空间,可以进行释放

	//不行
	int a = 10;
	int* p = &a;
	free(p);
	p = NULL;
	//这不是通过动态内存开辟申请的空间,不可以进行释放
	return 0;
}

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


#include 
#include 

int main()
{
	int* p = (int*)malloc(40);
	if (p == NULL)
	{
		printf("%s\n", strerrno(errno));
		return 0;
	}
	//使用内存
	int i = 0;
	//1
	for (i = 0; i < 5; i++)
	{
		*p = i + 1;
		p++;//这样会导致p所指向的地址发生变化,如果要简单地写成free(p),只会释放p后面的空间,前面通过动态内存开辟的空间无法释放
	//所以 要记住动态内存开辟时的初地址  
	}
	//释放
	free(p);
	p = NULL;
	return 0;
}

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

#include 
#include 
#include 
#include 

int main()
{
	int* p = (int*)malloc(40);
	if (p == NULL)
	{
		printf("%s\n", strerrno(errno));
		return 0;
	}
	//使用内存
	int i = 0;
	for (i = 0; i < 5; i++)
	{
		*(p+i) = i + 1;
	}
	//释放
	//不行
	//free(p);//一块动态内存开辟的空间不能多次释放
	//free(p);
    //行
	free(p);//释放之后一定要把指向首地址的指针改成指向NULL
	p = NULL;
	free(p);
	return 0;
}

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

错误示范:


#include 
#include 

void test()
{
	int* p = (int*)malloc(100);
	if (p == NULL)
	{
		return 0;
	}
	//使用
	
	//忘记释放,就会出现内存泄露的问题
	return 0;
}  

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

正确:


#include 
#include 

int* test()
{
	int* p = (int*)malloc(100);
	if (p == NULL)
	{
		return 0;
	}
	//使用
	
	//忘记释放,就会出现内存泄露的问题
	return 0;
}  

int main()
{ 
	int* ptr=test();
	free(ptr);
	return 0;
}

几道经典笔试题

1.

#include 
#include 

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

这段代码的结果是崩溃。

一共有两处错误:

第一处就是没有进行free释放自己申请的空间。

第二处就是函数使用了传值调用,而不是传址调用,在CSDN里学习函数的时候,提到了函数的传参方式有两种,传值调用相当于是对传过去的变量的一份临时拷贝,而传址调用才能真正意义上改变这个变量。在这里,p只是一个和str相同类型的指针,p指向一个字节大小为100的空间,但却和str没有关系。所以strcpy这个函数就会出错,没有足够的空间,就无法拷贝"hello world",当然也不能复制啦。

2.


#include 
#include 


char* GetMemory(void)
{
	char p[] = "hello world";
	return p;
}

void Test(void)
{
	char* str = NULL;
	str = GetMemory();
	printf(str);
}


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

这个代码的输出结果是

 这个代码有一处错误:

在Getmemory这个函数中,p是一个储存字符串"hello world"的字符串数组,返回p的值,p这个数组就会销毁,所有的用来创建GetMemory函数的空间都被收回,str指针虽然指向那个字符数组,但却没有访问的权限,故无法打印。

3.

#include 
#include 
#include 

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

这个代码输出的结果是

 这个代码输出的结果和预想的一样,但仍存在一个和前面两个题一样的错误就是申请的空间没有进行释放。

柔性数组

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

比如:

typedef struct s2
{
	char n;
	int arr[];
}s;

特点:

1. 结构体中柔性数组成员前面必须至少一个其他成员。

2. sizeof返回的这种结构体大小不包括柔性数组的内存

3.包含柔性数组成员的结构体用realloc()函数进行内存的动态分配,并且分配的内存应该大于·结构的大小,以适应柔性数组的预期大小。

柔性数组的使用

方法一:

int i = 0;
s* p = (s*)malloc(sizeof(s) + 100 * sizeof(int));
p->n = 100;
for (i = 0; i < 100; i++)
{
	p->arr[i] = i;
}
free(p)

方法二:

s* p = (s*)malloc(sizeof(s));
p->n = 100;
p->arr = (int*)malloc(sizeof(int) * p->n);
for (int i = 0; i < 100; i++)
{
	p->arr[i] = i;
}
free(p->arr);
p->arr = NULL;
free(p);
p = NULL;

第一个方法的优点:方便内存释放(只需要进行一次释放)

第二个方法的优点:有利于访问速度。

你可能感兴趣的:(c语言,开发语言,后端)