C语言:动态内存分配函数

本文详细介绍了C语言中的动态内存分配机制,包括malloc、free、calloc和realloc函数的用法,以及常见的内存管理错误,如NULL指针解引用、越界访问、内存泄漏等,并通过实例解析了几个经典试题。
摘要由CSDN通过智能技术生成

目录

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

2. malloc 和 free

2.1 malloc

2.2 free

3. calloc 和 realloc

3.1 calloc

3.2 realloc

4. 常见动态内存错误

4.1 对NULL指针解引用

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

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

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

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

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

5. 动态内存经典试题解析

5.1 题目1

5.2 题目2

5.3 题目3

5.4 题目4


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

在此之前,我们会通过创建局部变量,在栈区开辟空间。这种开辟空间的方式有两个缺陷:(1)开辟的空间大小是固定的;(2)数组长度指定后,不可调整。

但往往对于空间的需求,不仅仅是上述情况,有时候我们需要的空间在程序运行时才能知道,那数组在编译时开辟的固定空间就不能满足需求了。为此,C语言引入动态内存开辟,程序员可根据自身需求灵活开辟/扩大/缩小空间。

int main()
{
	int a = 1;            //在栈区开辟4个字节的空间
	int arr1[10] = { 0 }; //在栈区开辟40个字节的连续空间
	int arr2[] = {1,2,3}; //在栈区开辟14个字节的连续空间
	char ch = "abcdef";   //在栈区开辟7个字节的连续空间
	return 0;
}

2. malloc 和 free

2.1 malloc

(1)定义:向内存申请一块连续可用的空间,并返回指向该空间的指针

void* malloc (size_t size);

(2)参数:字节大小,即需要申请的空间的大小

(3)返回:指针,指向所申请的连续空间的起始位置

(4)要点:

  • 如果空间开辟成功,则返回指向开辟好的空间的指

  •  如果空间开辟失败,则返回NULL指针,因此该函数的返回值需要异常检查

  •  返回指针为void*,该函数并不知道开辟的空间具体是什么类型,需使用者明确

  •  如果参数为0,标准未定义,取决于编译器

(5)实现:

int main()
{
	//开辟空间
	int* p = (int*)malloc(10 * sizeof(int));

	//判断返回是否为NULL指针
	if(p == NULL)
	{
		perror("malloc");
		return 1;
	}

	//赋值
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		*(p + i) = i;
	}

	//使用
	for (int i = 0; i < 10; i++)
	{
		printf("%d ", *(p + i));
	}
	return 0;
}

2.2 free

C语言提供了另一个函数free,用于动态空间的释放和回收。

(1)定义:释放/回收指针所指向的动态空间。

void free (void* ptr);

(2)参数:指针,即指向动态空间的起始位置的指针

(3)返回:释放/回收动态空间

(4)要点:

  •  free函数释放/回收malloc/calloc/realloc开辟的动态空间
  • 如果参数ptr指向的空间不是动态开辟的,free函数的行为是未定义的
  • 如果参数ptr为NULL指针,则free函数什么也不做。
  • free函数仅释放指针所指向的动态空间,并不会改变指针。需要注意的是:指针未改变,指针所指向的空间被销毁,指针会变成野指针,因此指针需置为NULL。

(5)实现:

int main()
{
	//开辟空间
	int* p = (int*)malloc(10 * sizeof(int));

	//判断返回是否为NULL指针
	if(p == NULL)
	{
		perror("malloc");
		return 1;
	}

	//赋值
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		*(p + i) = i;
	}

	//使用
	for (int i = 0; i < 10; i++)
	{
		printf("%d ", *(p + i));
	}

	//释放空间
	free(p);

	//p指针置为NULL
	p = NULL;

	return 0;
}

3. calloc 和 realloc

3.1 calloc

(1)定义:向内存申请一块连续可用的空间,初始化为0,并返回指向该空间的指针。

void* calloc (size_t num, size_t size);

(2)参数:num为元素个数,size为元素大小

(3)返回:指针,指向所申请的连续空间的起始位置

(4)要点:

  • 函数功能是为num个大小为size的元素开辟一块空间,并且把空间的每个字节初始化为0
  • calloc函数与malloc函数的区别在于:在malloc函数功能基础上,对所开辟的空间进行初始化,初始化值为0

(5)实现:

int main()
{
	//开辟空间
	int* p = (int*)calloc(4, sizeof(int));
    
	//判断返回是否为NULL指针
	if (p == NULL)
	{
		perror("calloc");
		return 1;
	}

	//使用
	for (int i = 0; i < 4; i++)
	{
		printf("%d ", *(p + i));
	}

	//释放空间
	free(p);

	//指针置为NULL
	p = NULL;

	return 0;
}

3.2 realloc

realloc函数相较于上面的函数更加灵活,除了开辟动态空间外,还能扩大/缩小已开辟的空间。

(1)定义:

void* realloc (void* ptr, size_t size);

(2)参数:ptr为需要调整的内存地址,size为调整后的动态空间大小

(3)返回:调整后动态空间的起始位置

(4)要点:

  • 当ptr为NULL指针时,realloc函数和malloc函数功能一样,可用于开辟新的动态空间。
  • 当realloc函数调整内存空间时会存在两种情况:1)原有空间之后有足够大的空间:则扩展内存直接在原有空间上追加,原有空间数据不发生变化,返回旧地址;2)原有空间之后没有足够大的空间:在堆区找一块空间大小合适的连续空间,拷贝旧空间数据至新空间,释放旧空间,返回指向新空间的地址。
  •  如果调整内存空间失败,realloc函数返回NULL指针。因此对于realloc函数调整的内存空间,不应该用旧地址接收,避免内存空间调整失败,导致已有数据被销毁。

(5)实现: 

int main()
{
	//开辟动态空间
	int* p = (int*)malloc(10 * sizeof(int));

	//判断返回是否异常
	if (p == NULL)
	{
		perror("malloc");
		return 1;
	}
	//赋值
	for (int i = 0; i < 10; i++)
	{
		*(p + i) = i;
	}
	//打印
	for (int i = 0; i < 10; i++)
	{
		printf("%d ", *(p + i));
	}
    printf("\n");
	//空间不够,扩展空间
	int * ptr = (int*)realloc(p, 15 * sizeof(int));
	if (ptr == NULL)
	{
		perror("realloc");
		return 1;
	}
	p = ptr;   //新地址不为NULL指针的时候,赋值给旧指针变量
    ptr=NULL;
	
	//为新增的动态空间赋值
	for (int i = 0; i < 5; i++)
	{
		*(p + 10 + i) = i;
	}

	//打印
	for (int i = 0; i < 15; i++)
	{
		printf("%d ", *(p + i));
	}
	free(p);
	p = NULL;
	return 0;
}

4. 常见动态内存错误

4.1 对NULL指针解引用

malloc/calloc/realloc返回值可能为NULL指针,直接解引用会有问题,因此需要对动态空间开辟函数的返回值进行检查,非空才可解引用。

int main()
{
	int * p = (int*)malloc(10 * sizeof(int));
	*p = 20;  //返回值可能为NULL指针
	return 0;
}

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

int main()
{
	int* p = (int*)malloc(10 * sizeof(int));
	if (NULL == p)
	{
		perror("malloc");
		return 1;
	}
	for (int i = 0; i <= 12; i++)  //开辟40个空间,使用48个空间???
	{
		*(p + i) = i;
	}
	free(p);
	p = NULL;
	return 0;
}

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

int main()
{
	int a = 0;
	int* p = &a;
	free(p);
	return 0;
}

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

int main()
{
	int *p = (int*)malloc(10 * sizeof(4));
	if (NULL == p)
	{
		perror("malloc");
		return 1;
	}
	p++;  //p不再指向动态空间起始位置
	free(p);
	return 0;
}

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

int main()
{
	int* p = (int*)malloc(10 * sizeof(4));
	if (NULL == p)
	{
		perror("malloc");
		return 1;
	}
	free(p);
    p=NULL;
	free(p); //多次释放无意义
    p=NULL;
	return 0;
}

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

动态空间释放的方式:1)free函数释放;2)程序结束释放。需要注意的是:以往局部变量开辟的空间,出了作用域,内存空间会被销毁;动态开辟的空间即使出了作用域也不会被释放,直到整个系统程序结束才被销毁。动态开辟的空间一定要释放,并且正确释放,忘记释放不再使用的动态开辟空间会造成内存泄漏。

int main()
{
	int* p = (int*)malloc(10 * sizeof(4));
	if (NULL == p)
	{
		perror("malloc");
		return 1;
	}
	return 0;
}

5. 动态内存经典试题解析

5.1 题目1

  • str为指针变量,赋值为NULL
  • GetMemory函数调用为传值,形参p为实参str的临时拷贝,形参的变化不会影响实参。那么GetMemory(str)并不影响str为NULL指针。这时候,strcpy()相当于向空指针写入内容,程序会出现问题。
  • GetMemory函数开辟100个字节动态空间,返回起始位置,并赋值给指针变量p。根据以往的知识点:1)动态空间即使出了作用域也不会被销毁,直到系统程序结束,那么在调用GetMemory函数时,100个字节动态空间仍然存在;2)局部变量出作用域,内存空间会回收,则p变量不存在。
void GetMemory(char* p)
{
	p = (char*)malloc(100); //开辟100个字节动态空间,并返回起始位置
}

int main ()
{
	char* str = NULL;  //str指针变量为NULL;
	GetMemory(str);
	strcpy(str, "hello world");
	printf(str);
	return 0;
}

5.2 题目2

  • str为指针变量,赋值为NULL
  • GetMemory()函数中,返回数组p的起始地址。局部变量出作用域,其内存空间会被销毁,但是指向该空间的指针仍然存在,此时指针为野指针。
  • 调用函数并使用,相当于对野指针解引用,程序会出问题。
char* GetMemory(void)
{
	char p[] = "hello world";
	return p;
}
int main ()
{
	char* str = NULL;
	str = GetMemory();
	printf(str);
	return 0;
}

5.3 题目3

  • 对malloc函数的返回值未作异常检验,可能存在问题
  • 对malloc动态开辟的空间,未主动释放,会存在内存泄漏的问题
void GetMemory(char** p, int num)
{
	*p = (char*)malloc(num);
}
int main ()
{
	char* str = NULL;
	GetMemory(&str, 100);
	strcpy(str, "hello");
	printf(str);
	return 0;
}

5.4 题目4

  • 尽管这段代码在VS中有运行结果,但是存在问题的。
  • malloc函数动态开辟100个字节,返回起始位置,并赋值给str指针变量
  • 拷贝"hello"至开辟的动态空间,能成功打印"hello"
  • 释放开辟的100个字节的动态空间
  • 根据上面的知识点:free函数仅释放指针所指向的动态空间,并不会改变指针,即指针未改变,指针所指向的空间被销毁,指针会变成野指针。这里对野指针解引用,是有问题的。
int main()
{
	char* str = (char*)malloc(100); //malloc函数动态开辟100个字节,返回起始位置,并赋值给str指针变量
	strcpy(str, "hello"); //拷贝"hello"至开辟的动态空间,能成功打印"hello"
	printf(str);
	free(str); //释放开辟的100个字节的动态空间
	printf("\nhehe\n");
	if (str != NULL)
	{
		strcpy(str, "world");
		printf(str);
	}
	return 0;
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值