【内存】——动态内存管理
目录
前言:
在以前我们学习了内存开辟方式有:int a = 10; //在栈空间上开辟四个字节。char arr[10] = {0}; // 在栈空间上开辟了10个字节的连续空间。
但是这种开辟空间的方式有两个问题:1.空间的开辟大小必须是固定的,2.数组在声明的时候必须指定数组的大小长度。在一些特定的场景这中方式是不便的,这就需要试一下动态内存开辟了。
一:动态内存函数介绍
首先,在使用动态内存函数之前必须得包含其头文件(<stdlib.h>)
动态内存函数一共有四个函数:malloc函数 calloc函数 realloc函数 free函数
malloc内存函数:即内存开辟函数,开辟空间成功后其空间内的内容不被初始化。
calloc内存函数:内存开辟函数,开辟空间成功后其空间内的内容被初始化。
realloc内存函数:内存开辟/内存增容函数,可以开辟内存空间,又可增长内存空间。
free内存函数:内存释放函数,将所开辟的内存释放掉。
二:动态内存函数malloc与free
- malloc内存函数:即内存开辟函数,此函数开辟成功后,就会返回一个指向开辟好空间的指针,若开辟失败,则返回一个NULL指针,因此 malloc的返回值一定要做检查,并且malloc申请到空间后直接返回这块空间的起始地址,不会初始化空间的内容。
- free内存函数:内存释放函数,将所开辟的内存释放掉。需要注意的是,free内存函数只能释放动态开辟的内存,如果它的参数指向的空间不是动态的,那么 free 函数的行为是未定义的,如果它的参数是NULL指针,则此函数什么事情都不用做。
malloc 函数与 free 函数使用形式:
void* malloc(size_t num);
// num:所开辟 num 个字节
// 若函数开辟失败,则返回一个NULL指针,因此malloc的返回值一定要做检查
// 返回值的类型是 void* ,所以malloc函数事先并不知道开辟空间的类型,在使用的时候需要我们自己来选择
void* free(void* str);
// str:被释放空间的起始地址
如何使用:
#include<stdio.h>
#include<stdlib.h>
// malloc 与free
int main()
{
// malloc 开辟内存(含初始化) void* malloc(size_t size)
// free 内存释放 void* free(void* ptr)
int arr[10] = { 0 }; // 在栈上申请了40个字节,出了作用域被销毁
int* p = (int*)malloc(40); // 使用malloc 在堆上申请了40个字节,出了作用域不会被销毁
if (p == NULL) // 检查是否开辟空间成功
{
perror("malloc fail");
return ;
}
// 40个字节----10个整形
for (int i = 0; i < 10; i++) // 给开辟的空间赋值
{
p[i] = i * 2;
}
for (int i = 0; i < 10; i++)
{
printf("%d ", p[i]);
}
free(p); // 将开辟的内存释放
p = NULL;
}
// 运行结果:
// 0 2 4 6 8 10 12 14 16 18
查看malloc 函数所开辟的动态空间是否被初始化:
发现所开辟的空间并没有被初始化,是随机值。
三:动态内存函数 calloc
- calloc内存函数:是内存开辟函数。
- void* calloc(size_t num, size_t size);
- 功能:为 num 个大小为 size 的元素开辟一块空间,并把空间的每个字节初始化为0
- 注意:与 malloc 内存函数的区别只在于 calloc 内存函数会在返回地址之前把申请的空间的每个字节初始化为全0。
calloc 内存函数的使用形式:
void* calloc(size_t num , size_t size);
// 为num个大小为size的元素开辟空间
// 若开辟空间失败,就返回一个NULL指针,即calloc的返回值也一定要做检查
使用情况:
#include<stdio.h>
#include<stdlib.h>
// calloc
int main()
{
// calloc 开辟空间(含初始化) void* calloc(size_t num,size_t size)
// 为num个大小为size的元素开辟一块空间,并将空间内的每个字节初始化为0
// 与 malloc不同的是:只是calloc 会在返回地址之前把申请到的空间的每个字节初始化全为0
int* pc = (int*)calloc(10,4); // 开辟十个大小为4个字节的空间--->40个字节
if (pc == NULL) // 检查是否开辟空间成功
{
perror("calloc fail");
return;
}
for (int i = 0; i < 10; i++)
{
printf("%d ", pc[i]);
}
free(pc);
pc = NULL;
return 0;
}
查看结果:
发现calloc内存函数开辟成功后就直接将所开辟的空间初始化为0了。
四:动态内存函数 realloc
- realloc函数:内存开辟/调整内存空间的函数
- void* realloc(void* ptr , size_t size);
- realloc函数的出现使得动态内存管理更加灵活,有时当我们发现所开辟的空间太大或者太小时,为了合理的使用内存,就定会对内存的大小进行灵活的调整,那么realloc内存函数就可以做到对动态开辟内存大小的调整。
- ptr:要调整的内存地址;size:调整之后的新大小;返回值为调整之后的内存起始地址。
- 功能1——内存开辟:当ptr==NULL时,此时realloc内存函数得功能就与malloc内存函数得功能一模一样。
- 功能2——调整内存大小。
realloc内存函数的使用形式:
void* realloc(void* ptr , size_t size);
// ptr:要调整的内存地址;
// size:调整之后的新大小;
// 返回值为调整之后的内存起始地址。
如何使用:
#include<stdio.h>
#include<stdlib.h>
// realloc
int main()
{
// realloc 增长空间(开辟空间) void* realloc(void* ptr,size_t size)
// 对申请到的空间进行调整其大小,ptr是被调整的起始地址,size是调整后的新大小
int* pr = (int*)malloc(40); // 刚开始给pr开辟了40个字节(10个整形)
if (pr == NULL)
{
perror("malloc fail");
return;
}
for (int i = 0; i < 10; i++)
{
pr[i] = i * 2;
printf("%d ", pr[i]);
}
printf("\n");
int* tmp = (int*)realloc(pr, 80); // 发现不太够时,先创建一个变量给其增容,增容到80个字节(20个整形)
if (tmp == NULL) // 仍然检查其是否开辟空间成功
{
perror("realloc fail");
return;
}
pr = tmp; // 再将tmp指针变量赋给pr变量
for (int i = 0; i < 20; i++) // 此时 pr的空间大小就是80个字节
{
pr[i] = i * 2;
printf("%d ", pr[i]);
}
free(pr); // 将内存释放回收
pr = NULL;
}
运行结果:
五:常见的内存错误
5.1:对NULL指针的解引用操作
在使用内存函数(malloc,calloc,realloc)开辟空间之后,检查是否开辟空间成功。即检查其是否为空指针。
错误实例:
#include<stdio.h>
#include<stdlib.h>
int main()
{
int* p = (int*)malloc(40);
for(int i = 0; i<10; i++)
{
printf("%d ",p[i]);
}
free(p);
p = NULL;
}
正确实例:
#include<stdio.h>
#include<stdlib.h>
int main()
{
int* p = (int*)malloc(40);
if (p == NULL) // 检查是否开辟空间成功
{
perror("malloc fail");
return;
}
for(int i = 0; i<10; i++)
{
printf("%d ",p[i]);
}
free(p);
p = NULL;
}
5.2:对动态开辟空间的越界访问
不能超过内存函数所开辟的空间的大小
错误实例:
#include<stdio.h>
#include<stdlib.h>
int main()
{
int* p = (int*)malloc(40); // 内存函数共开辟了40个字节,即10个整形数据
if (p == NULL) // 检查是否开辟空间成功
{
perror("malloc fail");
return;
}
for(int i = 0; i<20; i++) // 数据越界(指针使用了20个整形数据,造成了数据越界)
{
printf("%d ",p[i]);
}
free(p);
p = NULL;
}
正确实例:
#include<stdio.h>
#include<stdlib.h>
int main()
{
int* p = (int*)malloc(40); // 内存函数共开辟了40个字节,即10个整形数据
if (p == NULL) // 检查是否开辟空间成功
{
perror("malloc fail");
return;
}
for(int i = 0; i<10; i++)
{
printf("%d ",p[i]);
}
free(p);
p = NULL;
}
5.3:对非动态开辟内存使用的free释放
使用free函数释放的空间必须是由动态内存函数(malloc calloc realloc)所开辟的动态内存空间。
错误实例:
#include<stdio.h>
#include<stdlib.h>
int main()
{
int a = 10;
int* pa = &a;
printf("%d ", *pa);
free(pa); // free函数不能释放非动态内存开辟的空间
pa = NULL;
}
5.4:使用free释放一块动态开辟内存的一部分
使用free释放动态内存空间时,一定要将动态内存空间的起始地址传给free,否则就释放空间不完全。
错误实例:
#include<stdio.h>
#include<stdlib.h>
int main()
{
int* p = (int*)malloc(40); // 内存函数共开辟了40个字节,即10个整形数据
if (p == NULL) // 检查是否开辟空间成功
{
perror("malloc fail");
return;
}
for (i = 0; i < 5; i++)
{
*p = i;
p++; // 注意:此时p已不是指向起始位置了
}
free(p); // 此时p只是释放的一部分动态内存开辟的空间
p = NULL;
}
正确实例:
#include<stdio.h>
#include<stdlib.h>
int main()
{
int* p = (int*)malloc(40); // 内存函数共开辟了40个字节,即10个整形数据
if (p == NULL) // 检查是否开辟空间成功
{
perror("malloc fail");
return;
}
int* tmp = p; // 将起始地址保存下来
for (i = 0; i < 5; i++)
{
*p = i;
p++; // 注意:此时p已不是指向起始位置了
}
p = tmp; // 再将起始地址tmp赋给p指针变量
free(p); // 此时p释放的全部的动态内存开辟的空间
p = NULL;
}
5.5:对同一块动态内存多次释放
错误实例:
#include<stdio.h>
#include<stdlib.h>
int main()
{
int* p = (int*)malloc(40);
if (p == NULL) // 检查是否开辟空间成功
{
perror("malloc fail");
return;
}
for(int i = 0; i<10; i++)
{
printf("%d ",p[i]);
}
free(p);
free(p); // 对同一块空间进行多次释放
p = NULL;
}
5.6:忘记释放
忘记释放动态内存空间,就会导致内存泄漏的问题。
错误实例:
#include<stdio.h>
#include<stdlib.h>
int main()
{
int* p = (int*)malloc(40);
if (p == NULL) // 检查是否开辟空间成功
{
perror("malloc fail");
return;
}
for(int i = 0; i<10; i++)
{
printf("%d ",p[i]);
}
}
六:经典笔试题
问运行下列main函数会有怎样的结果?
6.1:笔试题一
#include<stdio.h>
#include<stdlib.h>
void GetMemory(char* p)
{
p = (char*)malloc(100);
}
int main()
{
char* str = NULL;
GetMemory(str);
strcpy(str, "abcd");
printf(str);
return 0;
}
结果:此程序会报错 !!!
分析:在 GetMemory 函数中创建的动态内存函数返回的指针是 p,而p指针在出了GetMemory函数作用域之后就被销毁了,并不会使指针str空间开辟。所以直到程序结束,指针str指向的一直是NULL。
改正:
#include<stdio.h>
#include<stdlib.h>
void GetMemory(char** p)
{
*p = (char*)malloc(100);
}
int main()
{
char* str = NULL;
GetMemory(&str); // 传二级指针
strcpy(str, "abcd");
printf(str);
return 0;
}
6.2:笔试题二
#include<stdio.h>
#include<stdlib.h>
char* GetMemory_1()
{
char p[] = "hello world";
return p;
}
int main()
{
char* ptr = NULL;
ptr = GetMemory_1();
printf(ptr);
return 0;
}
结果:乱数据
分析:通过 GetMemory_1 函数返回一个指向 "hello world" 的指针,但是数组p出了此函数作用域就会被销毁了,并不会将这个正确的指针返回到ptr中。
改正:
#include<stdio.h>
#include<stdlib.h>
char* GetMemory_1()
{
static char p[] = "hello world";
return p;
}
int main()
{
char* ptr = NULL;
ptr = GetMemory_1();
printf(ptr);
return 0;
}
6.3:笔试题三
#include<stdio.h>
#include<stdlib.h>
void GetMemory_2(char** p, int num)
{
*p = (char*)malloc(num);
}
int main()
{
char* xtr = NULL;
GetMemory_2(&xtr, 100);
strcpy(xtr, "hello world");
printf(xtr);
return 0;
}
这一笔试题出现的唯一问题是:没有内存空间的释放。
改正:
#include<stdio.h>
#include<stdlib.h>
void GetMemory_2(char** p, int num)
{
*p = (char*)malloc(num);
}
int main()
{
char* xtr = NULL;
GetMemory_2(&xtr, 100);
strcpy(xtr, "hello world");
printf(xtr);
free(xtr);
xtr = NULL;
return 0;
}
6.4:笔试题四
#include<stdio.h>
#include<stdlib.h>
int main()
{
char* atr = (char*)malloc(100);
strcpy(atr, "hello world");
free(atr);
if (atr != NULL)
{
strcpy(atr, "hehe");
printf(atr);
}
return 0;
}
// 打印结果:hehe
这一笔试题出现的唯一问题是:在释放完内存空间之后没有对其置空NULL,在开辟空间之后没有检查是否开辟成功。
改正:
#include<stdio.h>
#include<stdlib.h>
int main()
{
char* atr = (char*)malloc(100);
if(atr==NULL)
{
perror("malloc fail");
return 1;
}
strcpy(atr, "hello world");
free(atr);
atr = NULL;
return 0;
}
七:C/C++中程序在内存中划分
在计算机程序中,大致可以将内存分配划分为几个区域:内核区,栈区,内存映射区,堆区,数据区,代码段区。其中内核区暂时不详解。
1. 栈区(stack):在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。 栈区主要存放运行函数而分配的局部变量、函数参数、返回数据、返回地址等。
2. 堆区(heap):一般由程序员分配释放, 若程序员不释放,程序结束时可能由操作系统(OS)回收 。分配方式类似于链表。
3. 数据段(静态区)(static)存放全局变量、静态数据。程序结束后由系统释放。
4. 代码段:存放函数体(类成员函数和全局函数)的二进制代码。
看具体分析图:
当我理解了这些变量在内存中的划分和使用时,这很有助于我们理解程序中的分析和情况。
总结:
今天我们首先了解了动态内存函数的基本信息和如何使用的,接着又指明了使用动态内存函数的常见几个问题。紧接着了解了一些经典的笔试题型,最后学习了C/C++的各种变量在程序内存中的划分等。
如果这篇文章对大家真的有帮助的话,可以的话动动小手点个赞关注一下吧亲!