你可能看到这个标题的时候是一头雾水,什么是动态开辟,动态开辟有什么应用,怎么实现动态开辟等等一系列的问题。今天这篇文章就带你了解动态内存的开辟。
一.内存分配存在的必要性
通过前面的学习,我们知道已经了解的内存开辟方式有如下两种:
?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;
}
?这样代码就完美了!以上就是动态内存申请的相关知识点,希望大家共同勉励,继续加油!
|