malloc函数
1.这个函数向内存申请一块连续可用的空间,并返回指向这块空间的指针。(首字节地址)• 如果开辟成功,则返回一个指向开辟好空间的指针。(首字节地址)• 如果开辟失败,则返回一个 NULL 指针,因此malloc的返回值一定要做检查。• 返回值的类型是 void* ,所以malloc函数并不知道开辟空间的类型,具体在使用的时候由 使用者自己 来决定。• 如果参数 size 为0,malloc的行为是标准是未定义的,取决于编译器。2.函数包含的头文件<stdlib.h>3.如果需要释放空间,需要使用free函数如果没有使用free释放,在程序退出的时候,也会由操作系统来回收
free函数
1.free函数用来释放动态开辟的内存。
• 如果参数 ptr 指向的空间不是动态开辟的,那free函数的行为是未定义的。• 如果参数 ptr 是NULL指针,则函数什么事都不做。• ptr是空间起始地址(首字节地址)2.函数包含的头文件<stdlib.h>3.空间释放后,ptr对应的指针变成野指针,应赋值NULL4.动态内存开辟后,使用结束时都应使用 free 将内存释放掉(不释放可能会造成 内存泄漏,进而引发错误 )
calloc函数
1.功能是为 num 个大小为 size 的元素开辟一块空间,并且把空间的每个字节初始化为0。2.这个函数向内存申请一块连续可用的空间,并返回指向这块空间的指针。(首字节地址)
• 如果开辟成功,则返回一个指向开辟好空间的指针。(首字节地址)• 如果开辟失败,则返回一个 NULL 指针,因此calloc的返回值一定要做检查。• 返回值的类型是 void* ,所以calloc函数并不知道开辟空间的类型,具体在使用的时候由 使用者自己 来决定。• 如果参数 size 为0,calloc的行为是标准是未定义的,取决于编译器。3.函数包含的头文件<stdlib.h>4.如果需要释放空间,需要使用free函数如果没有使用free释放,在程序退出的时候,也会由操作系统来回收
realloc函数
1.该函数就可以做到对动态开辟内存大小的调整。• ptr 是要调整的内存地址(首字节地址)• size 调整之后新大小• 返回值为调整之后的内存起始位置。如果开辟失败,则返回一个 NULL 指针,因此realloc的返回值一定要做检查。• 返回值的类型是 void* ,所以realloc函数并不知道开辟空间的类型,具体在使用的时候由 使用者自己 来决定。• 如果参数 size 为0,realloc的行为是标准是未定义的,取决于编译器。2.函数包含的头文件<stdlib.h>3.如果需要释放空间,需要使用free函数如果没有使用free释放,在程序退出的时候,也会由操作系统来回收
realloc在调整内存空间的是存在两种情况:• 情况1:原有空间之后有足够大的空间• 情况2:原有空间之后没有足够大的空间
情况1
当是情况1 的时候,要扩展内存就直接原有内存之后直接追加空间,原来空间的数据不发生变化。
返回的地址与ptr指向的地址一样
情况2
当是情况2 的时候,原有空间之后没有足够多的空间时,扩展的方法是:在堆空间上另找一个合适大小的连续空间来使用并且把原有空间里的数值复制到新的空间里,然后再删除原有旧的栈空间。这样函数返回的是一个新的内存地址。
返回的地址与ptr指向的地址不一样
int main()
{
int*p = (int*)malloc(5*sizeof(int));
if (p == NULL)
{
perror("malloc");
return 1;
}
//使用
int i = 0;
for (i = 0; i < 5; i++)
{
*(p + i) = i + 1;
}
//调整申请的堆上内存
int* ptr = (int*)realloc(p, 40);
if (ptr != NULL)
{
p = ptr;
ptr = NULL;
}
else
{
perror("realloc");
return 1;
}
//使用
for (i = 5; i < 10; i++)
{
*(p + i) = i+1;
}
for (i = 0; i < 10; i++)
{
printf("%d\n", *(p + i));// 1 2 3 4 5 6 7 8 9 10
}
//释放
free(p);
p = NULL;
return 0;
}
当realloc函数的第一个参数是NULL指针的时候,功能类似于malloc函数
常见的动态内存的错误分析
错误一 对NULL指针的解引用操作
没有经过判断 p是否等于NULL,有可能创建失败
错误二 对动态开辟空间的越界访问
![](https://i-blog.csdnimg.cn/blog_migrate/ad0563cefcbe98986c3a68d8d499a67b.png)
越界访问并赋值
错误三 对非动态开辟内存使用free释放
void test (){int a = 10 ;int *p = &a;free (p); //ok?}
p指向的空间不是动态内存。不在栈空间
错误四 使用free释放一块动态开辟内存的一部分
![](https://i-blog.csdnimg.cn/blog_migrate/4f0357a6a0dfc616d314855b1bd6d1d4.png)
这里只释放了一部分,并没有全部释放,这里的p是原有地址的大小加4个字节后的地址
错误五 对同一块动态内存多次释放
![](https://i-blog.csdnimg.cn/blog_migrate/23aa715931a4eb4f12bcc487c81e7de6.png)
同一块动态空间只能释放一次
错误六 动态开辟内存忘记释放(内存泄漏)
![](https://i-blog.csdnimg.cn/blog_migrate/9da56d0e628e5b0984deb9866b08b487.png)
这里text函数调用结束后,指针p变量销毁,但创建的栈空间还存在,这就变成了没有指向又没有释放的未知空间,称为 内存泄露,这是一种错误的情况
动态内存经典笔试题分析
题目一
这里将 NULL赋给了指针p,指针p又在其中开辟了动态内存但外面的指针str还是NULL,所以下面的行为都是发生不了的那100字节的空间也成了内存泄露
题目二
![](https://i-blog.csdnimg.cn/blog_migrate/784634abcacb9a059cd1d808af9d10f1.png)
GetMemory函数结束后,里面的数组p就销毁了,str就变成了一个野指针
题目三
![](https://i-blog.csdnimg.cn/blog_migrate/4ac283489001a94b25a42c36122f3a7f.png)
运行正常补充:free(str); str = NULL;
题目四
![](https://i-blog.csdnimg.cn/blog_migrate/283abfce8fca91166b8988c637ca16bc.png)
这里将 str 释放了,str就是野指针,后面是错误行为
柔性数组
• 结构中的柔性数组成员前面必须至少一个其他成员。• sizeof 返回的这种结构大小不包括柔性数组的内存。• 包含柔性数组成员的结构用malloc ()函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小。
#include<stdio.h>
#include<stdlib.h>
struct st_type
{
int i;
int a[0];//柔性数组成员
};
int main()
{
struct st_type* p = (struct st_type*)malloc(sizeof(struct st_type) + 10*sizeof(int));
if (p == NULL)
{
perror("malloc");
return 1;
}
int i = 0;
p->i = 100;
for (i = 0; i < 10; i++)
{
p->a[i] = i;
}
//我们希望结构中的a数组变长为60个字节
struct st_type* ptr = (struct st_type*)realloc(p, sizeof(struct st_type)+15*sizeof(int));
if (ptr != NULL)
{
p = ptr;
ptr = NULL;
}
else
{
perror("realloc");
return 1;
}
free(p);
p = NULL;
return 0;
}
除了上述的柔性数组也可以设计为下面的结构,也能完成同样的效果。
#include<stdio.h>
#include<stdlib.h>
struct st_type
{
int i;
int* a;
};
int main()
{
struct st_type* p = (struct st_type*)malloc(sizeof(struct st_type));
if (p == NULL)
{
perror("malloc\n");
return 1;
}
p->i = 100;
p->a =(int*)malloc(10*sizeof(int));
if (p->a == NULL)
{
perror("malloc");
return 1;
}
int i = 0;
for (i = 0; i < 10; i++)
{
p->a[i] = i;
}
//希望a指向的空间是15个整型
int* ptr = (int*)realloc(p->a, 15 * sizeof(int));
if (ptr == NULL)
{
perror("realloc");
return 1;
}
else
{
p->a = ptr;
ptr = NULL;
}
}
该结构没有上述结构便利
总结C/C++中程序内存区域划分
![](https://i-blog.csdnimg.cn/blog_migrate/73863d1d19be19ac3906f3c8259736b9.png)
C/C++程序内存分配的几个区域:
1. 栈区(stack):在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时 这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内 存容量有限。 栈区主要存放运行函数而分配的局部变量、函数参数、返回数据、返回地址等。2. 堆区(heap):一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。分配方 式类似于链表。3. 数据段(静态区)(static)存放全局变量、静态数据。程序结束后由系统释放。4. 代码段:存放函数体(类成员函数和全局函数)或者常量的二进制代码。