练习使用动态内存相关的4个函数:malloc、calloc、realloc、free

简介: 在了解使用动态内存相关的四个函数之前,我们先了解一下,为什么要有动态内存分配?

在了解使用动态内存相关的四个函数之前,我们先了解一下,为什么要有动态内存分配?


首先,我们已经掌握了一种开辟内存的方式,就是直接使用int i=20;但是这样开辟空间有两个特点,1:空间开辟大小是固定的,2:数组在创建时,必须设定数组的长度,数组空间的大小一旦确定就不能更改


可以申请和释放空间,这样就⽐较灵活了

malloc

void* malloc (size_t size);

这个函数向内存申请一块连续可用的空间,并返回指向这块空间的指针。


如果开辟成功,返回一个开辟成功的指针。


如果失败,则返回NULL。因此,在malloc使用之前必须检查是否为空指针。


返回类型为void*,因此,返回的类型由自己来决定。同时,size为0的时候,malloc的行为是标准未定义的,这取决于编译器。

free

free函数是专门用来做动态内存的释放和回收的。

void free (void* ptr);

free函数用来做动态内存的释放,如果ptr指向的空间不是动态内存,那么free的行为就是标准未定义的。如果ptr指向的空间为空,那么free什么事都不做。


malloc和free都声明在 stdlib.h 头⽂件中。


传递给free的是要释放空间的起始地址,例如下面的案例,传递的不是起始地址,就无法释放:

在free之后一定要置为空,否则会造成一些意想不到的灾难。

calloc

calloc的初始化如下:void* calloc (size_t num, size_t size);

它为每个大小为size的num字节开辟空间,并初始化为0。其与malloc的区别是它会将所有字节初始化为0。

realloc

有时会我们发现过去申请的空间太⼩了,有时候我们⼜会觉得申请的空间过⼤了,那为了合理的时

候内存,我们⼀定会对内存的⼤⼩做灵活的调整。那 realloc 函数就可以做到对动态开辟内存⼤

⼩的调整。


函数的原型如下:


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

ptr是要调整的内存地址,size为调整后的大小。返回值为调整后内存的起始位置。


这个函数调整原内存空间⼤⼩的基础上,还会将原来内存中的数据移动到新的空间。

realloc在调整内存空间的是存在两种情况:

情况1:原有空间之后有⾜够⼤的空间,空间续上,然后再返回起始空间的地址就可以了

情况2:原有空间之后没有⾜够⼤的空间

如果是情况一,增加空间直接追加在原有空间后面。 原有数据位置不发生变化,原有空间之后没有⾜够多的空间时,扩展的⽅法是:在堆空间上另找⼀个合适⼤⼩的连续空间来使⽤。(满足新的大小要求)会将原来的数据拷贝一份到新的空间,释放旧的空间(realloc会主动把这块空间释放掉)。这样函数返回的是⼀个新的内存地址。

还有一种情况,调整失败,返回的是空指针。如下:

如果realloc调整失败了,空指针放到p里面,p原来还维护20个字节,现在20个字节释放了,也找不到了。

所以选择用新的指针ptr来接收新的空间地址

realloc函数可以完成和malloc一样的功能:

realloc(NULL,20);==malloc(20);

下面举一个失败的案例:

 not enough space

动态内存的常见错误:


1对NULL指针的解引⽤操作

void test()
{
int *p = (int *)malloc(INT_MAX/4);
*p = 20;//如果p的值是NULL,就会有问题
free(p);
}

原因:malloc的返回值需要判断

修改:

void test()
{
int *p = (int *)malloc(INT_MAX/4);
if(p==NULL)
{
    perror("malloc");
    return 1;
}
assert(p);
*p = 20;//如果p的值是NULL,就会有问题
free(p);
}

对动态开辟空间的越界访问

void test()
{
int i = 0;
int *p = (int *)malloc(10*sizeof(int));
if(NULL == p)
{
exit(EXIT_FAILURE);
}
for(i=0; i<=10; i++)
{
*(p+i) = i;//当i是10的时候越界访问
}
free(p);
}

对⾮动态开辟内存使⽤free释放,是不能够运行的

使⽤free释放⼀块动态开辟内存的⼀部分

void test()
{
int *p = (int *)malloc(100);
p++;
free(p);//p不再指向动态内存的起始位置
}

必须是起始位置,报错。

对同一块动态内存多次释放

void test()
{
int *p = (int *)malloc(100);
free(p);
free(p);//重复释放
}

在释放完之后,及时把p置为空指针。


动态开辟内存忘记释放(内存泄漏)

void test()
{
int *p = (int *)malloc(100);
if(NULL != p)
{
*p = 20;
}
}
int main()
{
test();
while(1);

在test函数运行完成之后,就找不到malloc的一百个空间了。一旦出去,局部变量就销毁了。不用也不释放,就造成了内存泄漏的问题。


malloc,realloc,calloc申请的空间如果不想使用,都可以使用free来释放,当程序结束的时候,也会由操作系统释放。


尽量做到:谁申请的空间谁释放,如果不能释放,要告诉使用的人记得释放。


malloc和free成对出现。


但是架不住指针的空间可能提前释放,如下:


动态内存经典笔试题分析

str为空指针。GetMemory本身是传值调用,p内也是NULL。malloc出空间的地址放到p指针中,p销毁,还给操作系统。当回来的时候,str依然为空指针。空指针没有指向有效的空间,

当结束最上面一个程序运行的时候,str的空间已经还给操作系统了,str已经是空指针了。出现对空指针的解引用操作,程序崩溃,不会打印。

那么如果对这个程序进行修改,使其正确呢?那么我们把str的地址传给GetMemory,char*是一级指针变量,那么要用二级指针接收

我们对修改后的程序,进行解释。

首先 创建一个指针,叫作str。str里储存一个空指针NULL。p指针里储存了一个地址,就是ptr的地址,指向ptr。而p malloc出100个空间的大小,所以相当于str指向这块空间。

这个代码有什么问题呢? p所指向的空间还给操作系统。

上面这个代码,应该free(str);之后把str置为空。

如上图所示,在free之后置为空。

柔性数组

柔性数组有哪些特点呢?

结构中的柔性数组成员前⾯必须⾄少⼀个其他成员。

sizeof返回的这种结构⼤⼩不包括柔性数组的内存。

包含柔性数组成员的结构⽤malloc()函数进⾏内存的动态分配,并且分配的内存应该⼤于结构的⼤⼩,以适应柔性数组的预期⼤⼩。

柔性数组的使用:

#include <stdio.h>
#include <stdlib.h>
int main()
{
int i = 0;
type_a *p = (type_a*)malloc(sizeof(type_a)+100*sizeof(int));
//业务处理
p->i = 100;
for(i=0; i<100; i++)
{
p->a[i] = i;
}
free(p);
return 0;
}

用realloc让数组变大变小

柔性数组的优势:

#include <stdio.h>
#include <stdlib.h>
typedef struct st_type
{
int i;
int *p_a;
}type_a;
int main()
{
type_a *p = (type_a *)malloc(sizeof(type_a));
p->i = 100;
p->p_a = (int *)malloc(p->i*sizeof(int));
//业务处理
for(i=0; i<100; i++)
{
p->p_a[i] = i;
}
//释放空间
free(p->p_a);
p->p_a = NULL;
free(p);
p = NULL;
return 0;
}

上述 代码1 和 代码2 可以完成同样的功能,但是 ⽅法1 的实现有两个好处:

第⼀个好处是:⽅便内存释放

如果我们的代码是在⼀个给别⼈⽤的函数中,你在⾥⾯做了⼆次内存分配,并把整个结构体返回给用户。用户调⽤free可以释放结构体,但是用户并不知道这个结构体内的成员也需要free,所以你不能依靠用户来发现这个事。所以,如果我们把结构体的内存以及其成员要的内存⼀次性分配好了,并返回给用户⼀个结构体指针,用户做⼀次free就可以把所有的内存也给释放掉。

第⼆个好处是:这样有利于访问速度

连续的内存有益于提⾼访问速度,也有益于减少内存碎⽚。

————————————————


                           版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

                     

原文链接:https://blog.csdn.net/GISer_pearl/article/details/136996056

相关文章
|
1月前
|
C语言 C++
C语言 之 内存函数
C语言 之 内存函数
33 3
|
25天前
|
程序员 C++ 容器
在 C++中,realloc 函数返回 NULL 时,需要手动释放原来的内存吗?
在 C++ 中,当 realloc 函数返回 NULL 时,表示内存重新分配失败,但原内存块仍然有效,因此需要手动释放原来的内存,以避免内存泄漏。
|
21天前
|
存储 C语言
【c语言】字符串函数和内存函数
本文介绍了C语言中常用的字符串函数和内存函数,包括`strlen`、`strcpy`、`strcat`、`strcmp`、`strstr`、`strncpy`、`strncat`、`strncmp`、`strtok`、`memcpy`、`memmove`和`memset`等函数的使用方法及模拟实现。文章详细讲解了每个函数的功能、参数、返回值,并提供了具体的代码示例,帮助读者更好地理解和掌握这些函数的应用。
18 0
|
1月前
|
C语言 C++
c语言回顾-内存操作函数
c语言回顾-内存操作函数
39 0
|
1月前
|
存储 C语言 C++
来不及哀悼了,接下来上场的是C语言内存函数memcpy,memmove,memset,memcmp
本文详细介绍了C语言中的四个内存操作函数:memcpy用于无重叠复制,memmove处理重叠内存,memset用于填充特定值,memcmp用于内存区域比较。通过实例展示了它们的用法和注意事项。
62 0
|
1月前
一刻也没有为它哀悼~接下来登场的是动态内存分配的malloc与realloc以及free函数
一刻也没有为它哀悼~接下来登场的是动态内存分配的malloc与realloc以及free函数
63 0
|
3月前
|
存储 编译器 C语言
【C语言篇】数据在内存中的存储(超详细)
浮点数就采⽤下⾯的规则表⽰,即指数E的真实值加上127(或1023),再将有效数字M去掉整数部分的1。
361 0
|
20天前
|
存储 C语言
数据在内存中的存储方式
本文介绍了计算机中整数和浮点数的存储方式,包括整数的原码、反码、补码,以及浮点数的IEEE754标准存储格式。同时,探讨了大小端字节序的概念及其判断方法,通过实例代码展示了这些概念的实际应用。
41 1
|
24天前
|
存储
共用体在内存中如何存储数据
共用体(Union)在内存中为所有成员分配同一段内存空间,大小等于最大成员所需的空间。这意味着所有成员共享同一块内存,但同一时间只能存储其中一个成员的数据,无法同时保存多个成员的值。
|
29天前
|
存储 弹性计算 算法
前端大模型应用笔记(四):如何在资源受限例如1核和1G内存的端侧或ECS上运行一个合适的向量存储库及如何优化
本文探讨了在资源受限的嵌入式设备(如1核处理器和1GB内存)上实现高效向量存储和检索的方法,旨在支持端侧大模型应用。文章分析了Annoy、HNSWLib、NMSLib、FLANN、VP-Trees和Lshbox等向量存储库的特点与适用场景,推荐Annoy作为多数情况下的首选方案,并提出了数据预处理、索引优化、查询优化等策略以提升性能。通过这些方法,即使在资源受限的环境中也能实现高效的向量检索。