C语言中的内存管理详情

目录
  • 1.malloc
  • 2.内存泄露
  • 3.内存池
  • 4.理论
  • 5.代码数据结构
  • 6.代码
  • 7.blk->begin
  • 8.总结

内容提要:

大家写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内存管理内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • C语言深入细致讲解动态内存管理

    目录 为什么存在动态内存管理 动态内存函数的介绍 malloc free calloc realloc 常见的动态内存错误 对NULL指针的解引用操作 对动态开辟空间的越界访问 对非动态开辟内存使用free访问 使用free 释放一块动态开辟内存的一部分 对一块动态内存多次释放 对动态内存开辟忘记释放 柔性数组 小结 为什么存在动态内存管理 我们已经掌握的内存开辟方式有: int val = 20;//在栈空间上开辟四个字节 char arr[10] = { 0 };//在栈空间上开辟10个字节

  • C语言初识动态内存管理malloc calloc realloc free函数

    目录 一.为什么存在动态内存分配 二.动态内存函数的使用 1.malloc函数 (1)malloc的定义 (2)malloc函数的注意事项 (3)malloc函数的使用 2.calloc函数 (1)calloc函数的定义 (2)calloc函数的注意事项 (3)calloc函数的使用 3.realloc函数 (1)realloc函数的定义 (2)realloc函数的注意事项 (3)realloc函数的使用 总结 一.为什么存在动态内存分配 在c语言中我们目前掌握的内存开辟方式有: int val

  • C语言中储存类别与内存管理的深入理解

    储存类别 C语言提供了多种储存类别供我们使用,并且对应的有对应的内存管理策略,在了解C中的储存类型前,我们先了解一下与储存类型相关的一些概念. 1. 基础概念 对象:不同于面向对象编程中的对象的含义,C语言是面向过程编程,不存在这样对象的概念,这个对象指的是值储存所占据物理内存空间. 左值:左值是可以指定对象的表达式,它的最简单形式即为标识符,复杂的可以为为指针之类.一个表达式成为左值的前提是它确实指定了一块作为对象的储存空间,例如: int a = 1;//a作为标识符,也作基础表达式,指定了

  • 详解C语言之动态内存管理

    目录 开辟动态内存的函数 释放开辟的动态内存空间的函数 错误信息函数 具体使用例: 常见的动态内存错误 总结 先来了解一下动态管理内存所需用到的函数 开辟动态内存的函数 1.malloc函数:void* malloc(size_t size); 功能:开辟一块大小为size单位为字节的动态空间.若开辟成功返回函数开辟空间的无类型指针,若开辟失败则返回空指针NULL 2.calloc函数: void* calloc(size_t num, size_t size); 功能:开辟一块能容纳下num个

  • C语言动态内存管理介绍

    目录 前言: C 语言为内存的分配和管理提供了几个函数: 1.malloc() 用法 2.calloc() 用法 3.realloc() 与 free() 用法 前言: 简单记录一下,内存管理函数 为什么使用动态内存呢? 简单理解就是可以最大限度调用内存 用多少生成多少,不用时就释放而静止内存不能释放 动态可避免运行大程序导致内存溢出 C 语言为内存的分配和管理提供了几个函数: 头文件:<stdlib.h> 注意:void * 类型表示未确定类型的指针  1.malloc() 用法  分配一块

  • C语言动态内存管理分析总结

    目录 什么是动态内存分配 动态内存函数的介绍 free malloc calloc realloc 动态内存管理中常见的错误 对NULL指针的解引用操作 对动态开辟空间的越界访问 对非动态开辟内存使用free释放 使用free释放一块动态开辟内存的一部分 对同一块动态内存多次释放 动态开辟内存忘记释放(内存泄漏) 一些经典的笔试题 题目1 题目2 题目3 题目4 柔性数组 柔性数组的特点 柔性数组的优势 什么是动态内存分配 我们都知道在C语言中,定义变量的时候,系统就会为这个变量分配内存空间,而

  • C语言动态内存管理的实现

    目录 1. 摘要 2. 为什么存在动态内存管理 3. 动态内存函数 3.1 malloc 3.2 free 3.3 calloc 3.4 realloc 4. 常见的动态内存错误 5. 几个经典笔试题 参考答案 6. 参考文献 1. 摘要 本文主要详解C语言中的动态内存分配 2. 为什么存在动态内存管理 我们先来看一段变量的声明: double x = 1.000000; char str[] = "abcdef"; 好的,上述变量的声明有何特点呢? 请思考一下,我的朋友. 对,没错,

  • C语言中的内存管理详情

    目录 1.malloc 2.内存泄露 3.内存池 4.理论 5.代码数据结构 6.代码 7.blk->begin 8.总结 内容提要: 大家写C程序时,手工申请过内存吗?每次需要存储空间时都向操作系统申请吗?使用完申请到的内存后有把它还给操作系统吗?遇到过“段错误”吗?本文的主题和这一串问题有很大的关系. 1.malloc 手工申请内存使用malloc.先看一段例程. #include <stdio.h> #include <string.h> #include <st

  • 模拟实现C语言中的内存管理

    这里模拟了C语言中的内存管理,当我们要创建或者使用一个对象时,那么这个对象会调用retain方法,计数+1,当我们要释放对象,我们会调用free,这里注意要对计数记性判断,如果是0的话,那么就会销毁. #import <Foundation/Foundation.h> int cnt = 0; void fun (charchar * p) { printf("%c\n",p[0]); } charchar * retain1(charchar * p) { //retai

  • C语言中动态内存管理图文详解

    目录 1.动态内存开辟的原因 2.动态内存函数的介绍 2.1malloc和free 2.2calloc 2.3realloc 3.常见的动态内存错误 3.1对NULL指针的解引用操作 3.2对动态开辟空间的越界访问 3.3对非动态开辟内存使用free 3.4使用释放一块动态开辟内存的一部分 3.5对同一块动态内存多次释放 3.6动态开辟内存忘记释放(内存泄漏) 4.练习 4.1练习1 4.1练习2 4.3练习3 4.4练习4 5.C/C++程序的内存开辟 总结 1.动态内存开辟的原因 常见的内存

  • 详解C语言中动态内存管理及柔性数组的使用

    目录 一.malloc 二.free 三.calloc 四.realloc 1.realloc在扩容时的情况 2.realloc也能实现malloc功能 五.使用动态内存的常见错误 1.free空指针 2.对动态开辟的空间越界访问 3.对非动态开辟内容free 4.只free动态开辟空间的一部分 5.对同一块内存多次free 6.动态内存空间忘记释放(内存泄漏) 六.柔性数组 1.柔性数组的概念 2.柔性数组的特点 3.柔性数组的使用场景 4.柔性数组的优点 一.malloc 这个函数向堆区申请

  • C/C++内存管理详情

    目录 C/C++内存管理 1. C/C++内存分布 2. C语言中动态内存管理方式 2.1 malloc/calloc/realloc和free 3. C++内存管理方式 3.1 new/delete操作内置类型 3.2 new和delete操作自定义类型 4. operator new与operator delete函数 5. new和delete的实现原理 5.1.new 5.2.delete 5.3.new 数组 5.4.delete 数组 C/C++内存管理 内存管理是C++最令人切齿痛

  • C/C++中的内存管理小结

    前言 我们最初熟知的内存开辟方式: int val = 20: 在栈空间上开辟4个字节 char array[10]: 在栈空间上开辟10个字节的连续空间 上述开辟空间的方式有两个特点: 空间开辟大小是固定的. 数组在申明的时候,必须指定数组的长度,它所需要的内存在编译时分配. 但是对于空间的需求,不仅仅是上述的情况,有时候我们需要的空大小在程序运行时才能知道,那此时静态的开辟空间的方式就不能满足了,我们这时候只能试试动态内存开辟. 这篇博客就来带大家梳理一下C/C++中的内存管理. 一:C/C

  • 一文详解C++中动态内存管理

    目录 前言 1.C/C++程序的内存开辟 2.C语言中动态内存管理方式:malloc/calloc/realloc/free 2.1malloc.calloc.realloc区别? 3.C++内存管理方式 3.1 new/delete操作内置类型 3.2 new和delete操作自定义类型 3.3new和malloc处理失败 4.operator new与operator delete函数 4.1 operator new与operator delete函数 4.1.1 我们看看operator

  • C++ 动态内存管理详情解说

    目录 写在前面 C/C++ 内存分布 C语言内存管理方式 C++内存管理方式 C++为何增加了new 和 delete new 一个对象 new 一个数组 delete malloc & new 内置类型 自定义类型 operator new与operator delete函数 原理 为何出现这两个函数 delete & delete[] 内存池 定位 new 写在前面 我们知道C++是支持C语言的,也就是说,C语言里面的malloc等函数都可以在C++中使用,但是C++有支持了另外两个关

  • Java语言中的内存泄露代码详解

    Java的一个重要特性就是通过垃圾收集器(GC)自动管理内存的回收,而不需要程序员自己来释放内存.理论上Java中所有不会再被利用的对象所占用的内存,都可以被GC回收,但是Java也存在内存泄露,但它的表现与C++不同. JAVA中的内存管理 要了解Java中的内存泄露,首先就得知道Java中的内存是如何管理的. 在Java程序中,我们通常使用new为对象分配内存,而这些内存空间都在堆(Heap)上. 下面看一个示例: public class Simple { public static vo

  • C++嵌入式内存管理详情

    目录 一.Linux内核系统结构 二.查看Linux内存 1.cache 2.buffer 三.内存补齐 前言: 上一篇介绍了软件层面上的内存,并没有涉及很多底层的原理:但在实际工程中,部署一个项目往往需要考虑内存的占用,这里的内存也就是嵌入式板子上的内存:本篇文章就简单介绍一下嵌入式端的一个内存管理: 一.Linux内核系统结构 主要分为五大模块: 本次主要讲解内存管理模块,其他模块不做介绍: 二.查看Linux内存 在Linux环境下,可通过free -m查看内存使用情况: 下图是一台rk3

随机推荐