动态内存开辟函数的使用

你可能看到这个标题的时候是一头雾水,什么是动态开辟,动态开辟有什么应用,怎么实现动态开辟等等一系列的问题。今天这篇文章就带你了解动态内存的开辟。

一.内存分配存在的必要性

通过前面的学习,我们知道已经了解的内存开辟方式有如下两种:

 int a=10;//在栈区开辟了4个字节的空间,并赋值为10

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

前面这两种方法就是到目前位置我们定义变量和数组的方式,从这里我们可以看出这样的内存分配有两个不足之处:

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

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

因此为了能够灵活地使用内存,C语言提供了动态内存分配的能力,使得程序员能够灵活地使内存来设计程序,接下来我们来讲动态内存的使用方式。

2.动态内存的使用---->malloc、calloc、realloc、free函数

C语言提供了4个和动态内存开辟有关的函数:malloc、calloc、realloc、free,下面我们来依次介绍这几个函数的使用::

1.malloc:对应的函数原型如下:

void *malloc( size_t size );

Allocates memory blocks

从函数原型及解释可以看出,malloc函数的作用主要是开辟一块大小为size的内存块,并记录开辟的空间的首地址!而开辟的空间的内容是随机值!

#include<stdio.h>
#include<string.h>
#include<errno.h>
#include<stdlib.h>//malloc函数所在的头文件
//malloc函数的使用
void test()
{
	int* tmp=(int*)malloc(sizeof(int) * 10);
	if (NULL == tmp)
	{
		printf("%s\n", strerror(errno));
		return;
	}
	free(tmp);
	tmp = NULL;
}
int main()
{  
	test();
	return 0;
}

我们打开内存窗口并输入tmp的值,观察上述代码发生了什么事:

可以看到确实开辟了一块空间,空间里的值是随机值!这就是malloc函数的作用,当然,使用malloc函数的时候要注意以下几点:

1.malloc可能申请失败,一旦失败就会返回空指针,所以在使用malloc函数的返回值的时候要检查指针的合法性!

2.malloc函数申请的空间需要用free函数回收,否则会存在内存泄露的问题!

2.calloc:对应的函数原型如下:

void *calloc( size_t num, size_t size );

Allocates an array in memory with elements initialized to 0.

calloc和malloc函数本质上都是动态申请一块连续的空间,但不同的是calloc函数在申请空间的时候会把空间的内容初始化成0。


#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<string.h>
#include<errno.h>
#include<stdlib.h>//calloc函数所在的头文件
//calloc函数的使用
//void *calloc( size_t num, size_t size );
//第一个参数是初始化空间元素的个数
//第二个参数是初始化空间元素的大小
void test()
{
	int* tmp=(int*)calloc(10, sizeof(int));
	if (NULL == tmp)
	{
		printf("%s\n", strerror(errno));
		return;
	}
   free(tmp);
   tmp=NULL;
}

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

我们打开内存窗口观察申请的内存空间:

 我们确实看到了calloc确实也申请了一块空间,但和malloc不同的是,calloc把申请的空间的内容全部都初始化成了0!那么使用calloc函数要注意以下的几个地方

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

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

3.realloc:来看函数原型:

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

Reallocate memory blocks

realloc函数的功能就是重新分配内存空间,也正是因为有这个函数使得动态内存分配函数变得更加灵活,realloc函数使用的时候有几种情况:

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

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

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

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

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

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

realloc函数在使用的时候要注意以下几点:

1.realloc可能会失败,返回空指针,所以在使用之前要判断指针的合法性!

2.realloc申请的内存空间需要用free释放,否则存在内存泄露的问题!

4.free函数:先来看函数原型:

void free( void *memblock );

Deallocates or frees a memory block.

这个函数的功能就是释放由malloc、calloc、realloc动态申请的内存空间,使用的方法比较简单:

#include<stdlib.h>
#include<stdio.h>
//free的使用
int main()
{
   int* tmp=(int*)malloc(100);
    free(tmp);
     tmp=NULL;
   return 0;
}

 使用free函数的时候,要注意以下的几个点:

1.free只能释放malloc、realloc、calloc的动态内存函数开辟的内存,使用free释放局部变量的地址的行为是标准未定义的!

2.对于申请的动态内存,如果不及时使用free释放会造成内存泄露!

3.free(NULL)在语法层面是正确的,但没有什么实际意义

4.使用完free函数,要把记录释放位置的指针置空!

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

三.计算机的内存分布

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

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

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

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

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

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

四.动态开辟的易错问题--->笔试题

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

 这样代码就完美了!以上就是动态内存申请的相关知识点,希望大家共同勉励,继续加油!

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值