返回顶部
首页 > 资讯 > 后端开发 > 其他教程 >C语言中的内存管理详情
  • 511
分享到

C语言中的内存管理详情

2024-04-02 19:04:59 511人浏览 独家记忆
摘要

目录1.malloc2.内存泄露3.内存池4.理论5.代码数据结构6.代码7.blk->begin8.总结内容提要: 大家写C程序时,手工申请过内存吗?每次需要存储空间时都向操

内容提要:

大家写C程序时,手工申请过内存吗?每次需要存储空间时都向操作系统申请吗?使用完申请到的内存后有把它还给操作系统吗?遇到过“段错误”吗?本文的主题和这一串问题有很大的关系。

1.malloc

手工申请内存使用malloc。先看一段例程。

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
char *say_hi();

int main(int arGC, char *argv[])
{
   char *str = say_hi();
   printf("str = %s\n", str);
   free(str);
   return 0;
}

char *say_hi()
{
   char *ptr = (char *)malloc(100);
   char *str = "how are you?";
   strcpy(ptr, str);
  
   return ptr;
}

执行结果如下:

[root@localhost compiler-basic]# gcc -o t t.c -g -m32
[root@localhost compiler-basic]# ./t
str = how are you?

把修改成:

char *say_hi()
{
   char *ptr = (char *)malloc(100);
   char *str = "how are you?";
   strcpy(ptr, str);
  
   return str;
}

执行结果如下:

[root@localhost compiler-basic]# gcc -o t t.c -g -m32
[root@localhost compiler-basic]# ./t
str = how are you?
Segmentation fault (core dumped)

在第二版say_hi中,返回值是str,我们看到,打印出的数据是how are you?。然而,这只是偶尔正确。C语言的函数并不能总是正确返回非数字类型的局部变量的值。在程序变得复杂后,保不准某个时候这样的函数就不能返回预期数据。

Segmentation fault (core dumped),这又是怎么回事呢?str不是malloc申请到的内存空间,用free释放它导致错误。

2.内存泄露

用malloc申请了内存空间却不用free释放,会造成内存泄露。

在前面的第二版say_hi中,ptr指向的内存空间就被泄露了。在程序员看来,执行完say_hi后,ptr指向的内存就没有价值了;由于没有正确地释放它,操作系统认为它仍然在使用中,当其他进程申请内存时,不会把这片内存回收重新分配。

像这样的内存泄露越来越多,会一直多到耗尽所有的内存。但实际上,那些被泄露的内存是完全应该被回收再使用的。

内存泄露后,会成为操作系统和程序员都无法掌控的内存。

我们在手工申请内存、使用完毕之后,一定要释放内存。malloc和free犹如一对连体婴儿,总是一起使用。

3.内存池

前面的例子,在say_hi使用malloc,在main使用free。连体婴儿却只能出现在两个函数中,这很危险。一不留神,就会忘记释放内存。

每次申请内存都使用malloc,需要陷入内核,性能开销很大。

有朋友会说,我只是个小菜鸟,暂时还不需要考虑性能开销,只要我写的程序能跑就行。

哈哈,你并不是第一个这么想的人,我也是这样想的,所以我不厌其烦地、勤快地多次使用malloc。终于,在昨天,我遇到了非常烦人的段错误。

段错误发生在malloc中,导致错误的函数调用链不同,在测试数据中随便加几个字符后错误又消失。断点调试不管用,在前几十次执行malloc没有段错误,在后面几十次中的某一次执行malloc时才出现段错误。段错误出现在内核中。内核是不会有问题的,即使问题指向内核,那一定是向内核提供了错误的输入数据。

在束手无策快要绝望之前,我把原始的malloc换成了向内存池申请内存。先前发生的奇怪错误,再也没有出现了。

4.理论

内存池,是使用malloc申请的一段内存;进程需要内存空间时,从这段内存中拿一块去用;当这段内存被用完后,再使用malloc申请一段新内存;像这样重复这个过程。

很容易发现,内存池减少了使用malloc的次数;在进程结束前,程序员能方便地一次性释放这些内存。

5.代码数据结构

struct mblock{
   char *begin;
   char *avail;
   char *end;
};

typedef struct heap{
  struct mblock *last;
   struct mblock head;
} *Heap;

#define HEAP(hp) struct heap hp = { &hp.head }
Heap CurrentHeap;
struct heap ProgrameHeap;
int HeapAlloc(Heap hp, int size);
void *do_malloc(int size);

6.代码

char *HeapAlloc(Heap hp, int size){
   struct mblock *blk = NULL;
   blk = hp->last;
  
   while(size > blk->end - blk->avail){
       int m = 4096 + sizeof(struct mblock) + size;
       blk->next = malloc(m);
       blk = blk->next;
       if(blk == NULL){
           printf("内存耗尽\n");
           exit(-1);
        }
       blk->begin = blk->avail = (char *)(blk + 1);
       blk->end = (char *)blk + m;
       hp->last = blk;
    }
  
   blk->avail += size;
  
   return blk->avail - size;
}

void *do_malloc(int size){
  CurrentHeap = HEAP(ProgrameHeap);
   void *p = HeapAlloc(CurrentHeap, size);
   memset(p, 0, size);
  
   return 0;
}

要申请内存的时候,原来是使用malloc,现在,我们有了上面的这套内存管理机制后,就使用do_malloc来申请内存。

**解说
HEAP**
这个宏把heap的第一个成员last的值设置成第二个成员head的内存地址。

要熟悉这种{ &hp.head }初始化结构体的语法。

7.blk->begin

blk->begin = blk->avail = (char *)(blk + 1);

先看blk + 1。它表示,在blk的基础上,往后移动sizeof(struct mblock)个字节。指针的加减就是这么计算的。

blk指向一段m个字节的内存空间,这段内存空间的前sizeof(struct mblock)个字节存储一个mblock结构。怎么理解这个结构?它是这段内存空间的元数据。了解文件系统的实现机制的朋友会很容易理解这一点。

从元数据中,获取begin、avail、end。

如果把blk->begin做如下修改。

blk->begin = blk->avail = (char *)(blk);

怎么样?我们来推演一番。

  • 第一次执行HeapAlloc,申请了一段内存,这段内存的前面是元数据;返回给进程的是这段内存的开始地址,也就是元数据的开始地址。
  • 执行memset(p, 0, size);,元数据被擦除。
  • 再次执行HeapAlloc,元数据end、avail不再是前一次执行HeapAlloc后设置的值,无法知晓内存池是否还有内存可以分配;已经乱套了。

blk->end最后一个问题,理解下面的代码。

blk->end = (char *)blk + m;

blk->end表示新申请的这片内存的末尾地址。

末尾地址等于初始地址加上这片内存的长度。看看上面的代码是不是这个意思。

(char

)blk + m就是这个意思。而(char

)(blk + m)就不是这个意思。

blk是这片内存的第一个字节,(char *)blk + m-1是这片内存的最后一个字节。

8.总结

每个内存池的开头都有一个mblock,存储这个内存池的元数据(begin、avail、end、next)。进程需要内存时,先向内存池申请。当前内存池容量不够时,再向系统申请一个内存池。把这个内存池连接到前一个内存池的元数据的next上。

一个内存池耗尽后,并非全部空间都被使用了。没有被利用的空间,在当前机制下,被浪费了。以后再找机会优化

所有的内存池构成一个单链表。当进程完成它的功能后,在结束前,遍历这个单链表,从元数据中获取begin,然后调用free(begin)就能释放所有的内存。

到此这篇关于C语言中的内存管理详情的文章就介绍到这了,更多相关C内存管理内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

--结束END--

本文标题: C语言中的内存管理详情

本文链接: https://lsjlt.com/news/148574.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • C语言中的内存管理详情
    目录1.malloc2.内存泄露3.内存池4.理论5.代码数据结构6.代码7.blk->begin8.总结内容提要: 大家写C程序时,手工申请过内存吗?每次需要存储空间时都向操...
    99+
    2024-04-02
  • C/C++内存管理详情
    目录C/C++内存管理1. C/C++内存分布2. C语言中动态内存管理方式2.1 malloc/calloc/realloc和free3. C++内存管理方式3.1 new/del...
    99+
    2024-04-02
  • C语言与C++中内存管理详解
    目录内存分布动态内存管理方式-堆区C语言动态内存管理C++动态内存管理new和delete的用法operator new与operator delete函数new和dele...
    99+
    2024-04-02
  • 详解C语言中的动态内存管理
    目录一、动态内存管理1.1为什么要有动态内存管理1.2动态内存介绍1.3常见的动态内存错误一、动态内存管理 1.1为什么要有动态内存管理 1.1.1  在c语言中我们普通的...
    99+
    2022-12-12
    C语言动态内存管理 C语言 内存管理 C语言 内存
  • C++嵌入式内存管理详情
    目录一、Linux内核系统结构二、查看Linux内存1.cache2.buffer三、内存补齐前言: 上一篇介绍了软件层面上的内存,并没有涉及很多底层的原理;但在实际工程中,部署一个...
    99+
    2024-04-02
  • C语言与C++中内存管理的方法
    这篇文章主要介绍了C语言与C++中内存管理的方法的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇C语言与C++中内存管理的方法文章都会有所收获,下面我们一起来看看吧。内存分布主要段及其分布每个程序运行起来以后,它...
    99+
    2023-06-30
  • C语言中动态内存管理图文详解
    目录1.动态内存开辟的原因2.动态内存函数的介绍2.1malloc和free2.2calloc2.3realloc3.常见的动态内存错误3.1对NULL指针的解引用操作3.2对动态开...
    99+
    2024-04-02
  • 详解C语言之动态内存管理
    目录开辟动态内存的函数释放开辟的动态内存空间的函数错误信息函数具体使用例: 常见的动态内存错误总结先来了解一下动态管理内存所需用到的函数 开辟动态内存的函数 1.mallo...
    99+
    2024-04-02
  • JVM内存管理之JAVA语言的内存管理详解
    引言内存管理一直是JAVA语言自豪与骄傲的资本,它让JAVA程序员基本上可以彻底忽略与内存管理相关的细节,只专注于业务逻辑。不过世界上不存在十全十美的好事,在带来了便利的同时,也因此引入了很多令人抓狂的内存溢出和泄露的问题。可怕的事情还不只...
    99+
    2023-05-31
    jvm 内存管理 java
  • C++动态内存管理详情解说
    目录写在前面C/C++ 内存分布C语言内存管理方式C++内存管理方式C++为何增加了new 和 deletenew 一个对象new 一个数组deletemalloc &am...
    99+
    2024-04-02
  • C语言与C++内存管理超详细分析
    目录一、内存1.1 内存四区1.2 使用代码证实内存四区的底层结构二、malloc 和 free2.1 malloc 和 free 的使用2.2 内存泄漏与安全使用实例与讲解三、ne...
    99+
    2024-04-02
  • 温故C语言内存管理
    1. 内存管理简介 在计算机系统,特别是嵌入式系统中,内存资源是非常 有限的。尤其对于移动端开发者来说,硬件资源的限制使得其在程序设计中首要考虑的问题就是如何 有效地管理内存资源。 ...
    99+
    2024-04-02
  • Go语言内存管理详解
    Go语言内存管理详解 Go语言作为一种现代化的编程语言,自带了垃圾回收器,这使得开发者无需手动管理内存,极大地简化了内存管理的复杂度。本文将详细介绍Go语言的内存管理机制,并通过具体的...
    99+
    2024-04-02
  • C++ 内存管理如何与 C 语言的内存管理进行交互?
    c++++ 内存管理与 c 语言的交互:兼容性:c++ 与 c 语言兼容,可以使用 c 中的指针和数组。指针和数组:c++ 指针和数组与 c 语言中类似,但 c++ 允许通过指针直接操纵...
    99+
    2024-05-24
    c c++
  • 不同语言中内存管理与Go语言内存管理的差异
    go 语言的内存管理与传统语言(如 c++++、java)不同:传统语言:采用手动内存管理,程序员负责分配和释放内存块。go 语言:采用垃圾回收(gc),自动管理内存,程序员无需手动管理...
    99+
    2024-04-11
    go语言 内存管理 c++
  • C语言/C++内存管理是什么
    本篇内容介绍了“C语言/C++内存管理是什么”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!一、内存在计算机中,每个应用程序之间的内存是相互独...
    99+
    2023-06-16
  • C语言动态内存管理的实现
    目录1. 摘要2. 为什么存在动态内存管理3. 动态内存函数3.1 malloc3.2 free3.3 calloc3.4 realloc4. 常见的动态内存错误5. 几个经典笔试题...
    99+
    2024-04-02
  • 怎么在C语言中实现内存管理
    这篇文章给大家介绍怎么在C语言中实现内存管理,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。C语言是什么C语言是一门面向过程的、抽象化的通用程序设计语言,广泛应用于底层开发,使用C语言可以以简易的方式编译、处理低级存储器...
    99+
    2023-06-14
  • C语言 超详细梳理总结动态内存管理
    目录一.为什么存在动态内存分配二.动态内存函数的介绍1.malloc和free2.calloc3.realloc三.常见的动态内存错误1.对NULL指针的解引用操作2.对动态开辟空间...
    99+
    2024-04-02
  • 【C语言进阶】动态内存管理
    动态内存管理 1.为什么存在动态内存分配2.动态内存函数的介绍2.1malloc和free2.2calloc2.3realloc 3.常见的动态内存错误3.1 对NULL指针的解引用操作...
    99+
    2023-10-25
    c语言 开发语言 1024程序员节
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作