动态内存分配函数详解以及代码示例

变量内存分配

①栈区 —— 局部变量 —— 向低地址生长 —— 自动释放 —— 其操作方式类似于数据结构中的栈。

②堆区 —— 向高地址生长 —— 手动分配、释放的存储区 —— malloc,free —— 它与数据结构中的堆是两回事,分配方式倒是类似于链表

③全局/静态存储区static —— 全局变量,静态变量,程序运行结束后自动释放

④常量存储区const —— 常量字符串储存在这里。储存在常量区的只读不可写。程序运行结束后自动释放

⑤代码区 —— 存放函数体的二进制代码。

  • 静态内存分配:编译时分配,包括:全局、静态全局、静态局部
  • 动态内存分配:运行时分配:包括:栈(局部变量),堆(C语言常用到的变量被动态地分配到内存当中:malloc,calloc,realloc,free函数)

——> const修饰的全局变量也储存在常量区

——> const修饰的局部变量依然在上。

这里涉及到静态分配和动态分配,静态分配我们都知道是os在栈区自动开辟空间,而后系统自动释放。比如:

int a=10;//在栈区开辟了4个字节的空间,并赋值为10
char arr[10]={
    
    0};//在栈区开辟了10个字节的空间,并赋值为0

由上可见,静态分配有两个不足的地方:

1.内存空间由操作系统分配,程序员无法自由使用

2.内存空间一旦分配完成,空间固定,无法灵活地变动和按需更改空间的大小!

那么就需要用到动态分配来解决不足之处。

动态分配

在C语言提供了4个和动态内存开辟有关的函数:malloc、calloc、realloc、free

malloc函数

void *malloc( size_t size );
//开辟成功,则返回一个指向开辟好空间的指针,要根据赋值指针转换类型
//开辟失败,则返回一个NULL指针,因此要检查malloc返回值合法性
//若参数 size 为0,malloc的行为是标准是未定义
//malloc函数申请的空间需要用free函数回收,否则会存在内存泄露的问题

malloc函数的作用主要是开辟一块大小为size的内存块,并记录开辟的空间的首地址,而开辟的空间的内容是随机值!

calloc函数

void *calloc( size_t num**,** size_t size );
//函数的功能是为num个大小为size的元素开辟一块空间,并且把空间的每个字节初始化为0
//calloc和malloc函数本质上都是动态申请一块连续的空间,但不同的是calloc函数在申请空间的时候会把空间的内容初始化成0

1.calloc函数申请空间可能失败,一旦失败calloc返回空指针,所以在使用calloc函数之前要注意判断指针的合法性!

2.calloc申请的空间需要用free函数释放!

realloc函数

void realloc( void **memblock**,** size_t size );
//realloc函数的功能就是重新分配内存空间,让动态内存管理更加灵活
//realloc可能会失败,返回空指针,所以在使用之前要判断指针的合法性!
//realloc申请的内存空间需要用free释放,否则存在内存泄露的问题!

realloc函数使用的时候有几种情况:

情况一:调整的空间比原来的空间小

这种情况下,realloc函数会丢弃多余的空间,将可使用的空间调整为你指定大小的空间

情况二:调整的空间比原来的空间大

这种情况就比较复杂,具体还要分成两种情况讨论!

1.假设已经开辟的空间的后面的空间足够,那么realloc利用的就是后续的空间,此时返回的还是原来的地址

2.假设后面空间已经不够使用,那么realloc就会重新寻找一块新的空间,拷贝原来的数据到新的空间,并把原来的空间释放!

free函数

void free( void **memblock* );
//专门是用来使动态内存的释放和回收
//如果参数指向的空间不是动态开辟的,那free函数的行为是未定义的
//如果参数是NULL指针,则函数什么事都不做

简单介绍完了这几个函数,接下来我们来讲讲计算机内存的分布、以及动态内存分配可能出现的常见错误!

计算机的内存分布

计算机的内存分布相对复杂,现阶段我们只需了解三大区:栈区、堆区、静态区

栈区:存储局部变量和局部数组等等,特点是创建时由系统自动创建,出作用域时自动销毁!

堆区:由程序员使用动态内存开辟函数申请,使用结束后由程序员手动释放

静态区:存储全局变量、静态变量、常量字符串等等

因为堆区的动态内存的生命周期是在手动释放之前都一直存在,所以有的时候要返回的结果不只是一个值的情况下,我们就可以使用动态开辟的内存存储数据,最后把数据带回来。

正因为动态内存开辟十分的方便和灵活,所以在使用的时候也有很多要注意的问题。

动态开辟的易错问题

1.对空指针的解引用操作:

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<string.h>
#include<errno.h>
#include<stdlib.h>
#include<limits.h>
//对空指针的解引用操作
void test(){
    
    
	int* tmp = (int*)malloc(INT_MAX*sizeof(int));
	for (int i = 0; i < 10; i++){
    
    
		*(tmp + i) = i;
		printf("%d ", *(tmp + i));
	}
	free(tmp);
	tmp = NULL;
}
int main(){
    
    
	test();
	return 0;
}

这段代码乍一看好像没有什么大问题,但其实运行起来,程序实际会崩溃!因为我们对空指针进行了解引用操作!为什么会对空指针进行解引用?本质原因就是计算机分配不到那么大的空间给我们

INT_MAX:大小是2147483647,而上面的代码向堆区申请了能够存放这么多个整型元素的空间,但是堆区并没有那么大的空间可以分配,因此申请失败返回NULL指针,而接下来对NULL指针的解引用引发了程序崩溃!

所以,我们在使用动态内存分配函数返回的地址的时候,一定要检查合法性!正确的代码如下:

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<string.h>
#include<errno.h>//errno所在的头文件
#include<stdlib.h>
#include<limits.h>
/对空指针的解引用操作
void test(){
    
    
	int* tmp = (int*)malloc(INT_MAX*sizeof(int));
	if (NULL == tmp)//检查指针的合法性!{
    
    
		printf("%s\n", strerror(errno));//报出错误信息,并停止当前函数
		return;
	}
	for (int i = 0; i < 10; i++){
    
    
		*(tmp + i) = i;
		printf("%d ", *(tmp + i));
	}
	free(tmp);
	tmp = NULL;
}
int main(){
    
    
	test();
	return 0;
}

再来看一段经典的对空指针解引用的错误代码,也曾经是一家公司的笔试题!

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<string.h>
#include<errno.h>//errno所在的头文件
#include<stdlib.h>
#include<limits.h>
void GetMemory(char* p){
    
    
	p = (char*)malloc(100);
}
void test(){
    
    
	char* str = NULL;
	GetMemory(str);
	strcpy(str, "helloworld");
	printf(str);
}
int main(){
    
    
	test();
	return 0;
}

这段代码的本意是动态申请100个字节的空间,并让str指向这块动态申请的空间,在使用strcpy函数把helloworld拷贝进去,最后在把拷贝后的结果打印出来,乍一看好像没有什么大问题,但实际程序还是会崩溃!因为str还是空指针!

问题就出在GetMemory函数上,因为这个函数是值传递的,也就是说,p只是str的一份拷贝,对拷贝的修改并不会影响到原来的str,那么strcpy函数的第一个参数是NULL!因此这也就是对NULL指针进行解引用操作!

另外,这段代码还存在内存泄露的问题!由于记录申请空间起始地址的局部变量p在函数调用结束后自动销毁,一旦函数调用结束,我们就没办法获取这块内存的起始位置,那么这块内存我们就不能使用了,内存就泄露了!这是这段代码还存在的一个问题!这段代码有两种解决方案:

方案一:传递str的地址改变str

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<string.h>
#include<errno.h>//errno所在的头文件
#include<stdlib.h>
#include<limits.h>
void GetMemory(char** p){
    
    
	*p = (char*)malloc(100);
}
void test(){
    
    
	char* str = NULL;
	GetMemory(&str);
	strcpy(str, "helloworld");
	printf(str);
    free(str);
    str=NULL;
}
int main(){
    
       
	test();
	return 0;
}

解决方案二:用返回一个动态申请的指针赋值给str

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<string.h>
#include<errno.h>//errno所在的头文件
#include<stdlib.h>
#include<limits.h>
char* GetMemory(){
    
    
	char* p = (char*)malloc(100);
	return p;
}
void test(){
    
    
	char* str = NULL;
	str = GetMemory();
   if (NULL == str){
    
    
		printf("%s\n", strerror(error));
		return;
	}
	strcpy(str, "helloworld");
	printf("返回值的方式 :");
	printf(str);
    free(str);
    str=NULL;
}
int main(){
    
    
	test();
	return 0;
}

这里我们通过使用返回值的方式改变str,同样也可以起到改变str的作用!

2.返回局部变量的地址

我们知道,局部变量出了作用域就会被销毁,那么有这么一段如下的代码:

int* test(){
    
    
	int a = 10;
	return &a;
}
int main(){
    
       
	int* pa = test();
	return 0;
}

因为出了函数以后,a的空间被回收了,那么原先指向存放a空间的地址里的内容就是随机的了,所以不要返回局部变量的地址!但是可以返回局部变量的值!返回局部变量的值是正确的行为!

3.动态申请的内存空间使用完没有free释放

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<string.h>
#include<errno.h>//errno所在的头文件
#include<stdlib.h>
#include<limits.h>
char* GetMemory(){
    
    
	char* p = (char*)malloc(100);
	return p;
}
void test(){
    
    
	char* str = NULL;
	str = GetMemory();
	if (NULL == str){
    
    
		printf("%s\n", strerror(errno));
		return;
	}
	strcpy(str, "helloworld");
	printf(str);
}
int main(){
    
    
	test();
	return 0;
}

这段代码是可以正常运行,但这段代码还有不足地地方就是使用完动态分配地内存没有free释放,这样会造成内存泄露导致可用的内存越来越少!正确的代码如下:

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<string.h>
#include<errno.h>//errno所在的头文件
#include<stdlib.h>
#include<limits.h>
char* GetMemory(){
    
    
	char* p = (char*)malloc(100);
	return p;
}
void test(){
    
    
	char* str = NULL;
	str = GetMemory();
	if (NULL == str){
    
    
		printf("%s\n", strerror(errno));
		return;
	}
	strcpy(str, "helloworld");
	printf(str);
    free(str);
    str=NULL;
}
int main(){
    
    
	test();
	return 0;
}

非常感谢__cplusplus博主的文章,以上大幅改自https://blog.csdn.net/qq_56628506/article/details/122785722,解决知识混淆点,再次感谢!


总结

1.动态分配相关函数malloc、calloc、realloc、free,可以灵活分配内存空间,使用完之后必须free

2.例malloc开辟空间,参数size不能过大,超过堆区空间则分配失败,一般采用sizeof函数创建所需空间大小

3.封装函数开辟内存空间,返回值必须是该变量地址,否则NULL

4.free完之后,要把记录释放位置的指针置空

猜你喜欢

转载自blog.csdn.net/qq_44333320/article/details/125960380
今日推荐