通过Malloc 和 Free 的具体实现 加深对C指针 的理解(笔记)

news2025/1/15 13:23:33

【彻底搞懂C指针】Malloc 和 Free 的具体实现

  • https://danluu.com/malloc-tutorial/

image.png

  • 进程间的通信 : ①共享内存 ② 消息传递 (内核实现)

分配策略 (实现方面)

by DUCK
image.png

sbrk()

malocal实现的主要函数
man sbrk 查看

数据结构

image.png
image.png

一个参考代码

  • https://github.com/danluu/malloc-tutorial/tree/master
#include <assert.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
// Don't include stdlb since the names will conflict?

// TODO: align

// sbrk some extra space every time we need it.
// This does no bookkeeping and therefore has no ability to free, realloc, etc.
void *nofree_malloc(size_t size) {
  void *p = sbrk(0);
  void *request = sbrk(size);
  if (request == (void*) -1) { 
    return NULL; // sbrk failed
  } else {
    assert(p == request); // Not thread safe.
    return p;
  }
}


// 单链表
struct block_meta {
  size_t size;
  struct block_meta *next;
  int free;
  int magic;    // For debugging only. TODO: remove this in non-debug mode.
};

#define META_SIZE sizeof(struct block_meta)

void *global_base = NULL;

// Iterate through blocks until we find one that's large enough.
// TODO: split block up if it's larger than necessary
struct block_meta *find_free_block(struct block_meta **last, size_t size) {
  struct block_meta *current = global_base;
  while (current && !(current->free && current->size >= size)) {
    *last = current;
    current = current->next;
  }
  return current;
}

struct block_meta *request_space(struct block_meta* last, size_t size) {
  struct block_meta *block;
  block = sbrk(0);
  void *request = sbrk(size + META_SIZE);
  assert((void*)block == request); // Not thread safe.
  if (request == (void*) -1) {
    return NULL; // sbrk failed.
  }
  
  if (last) { // NULL on first request.
    last->next = block;
  }
  block->size = size;
  block->next = NULL;
  block->free = 0;
  block->magic = 0x12345678;
  return block;
}

// If it's the first ever call, i.e., global_base == NULL, request_space and set global_base.
// Otherwise, if we can find a free block, use it.
// If not, request_space.
void *malloc(size_t size) { // 定义一个名为malloc的函数,它接受一个size_t类型的参数size,并返回一个void指针。
  struct block_meta *block; // 定义一个指向block_meta结构的指针block。
  // TODO: align size? // 这是一个待办事项,提示开发者可能需要对size进行对齐。

  if (size <= 0) { // 如果请求的内存大小小于或等于0,
    return NULL; // 则返回NULL。
  }

  if (!global_base) { // 如果全局变量global_base为空(即这是第一次调用malloc),
    block = request_space(NULL, size); // 则请求分配size大小的内存空间,并将返回的内存块的地址赋值给block。
    if (!block) { // 如果内存分配失败(即request_space返回NULL),
      return NULL; // 则返回NULL。
    }
    global_base = block; // 将global_base设置为新分配的内存块的地址。
  } else { // 如果global_base不为空(即已经有内存块被分配过),
    struct block_meta *last = global_base; // 定义一个新的指针last,并将其初始化为global_base。
    block = find_free_block(&last, size); // 在已分配的内存块中查找一个足够大的空闲块。
    if (!block) { // 如果没有找到足够大的空闲块,
      block = request_space(last, size); // 则请求分配新的内存空间。
      if (!block) { // 如果内存分配失败,
	return NULL; // 则返回NULL。
      }
    } else {      // 如果找到了一个足够大的空闲块,
      // TODO: consider splitting block here. // 这是一个待办事项,提示开发者可能需要在这里将找到的空闲块进行分割。
      block->free = 0; // 将空闲块的状态设置为已使用。
      block->magic = 0x77777777; // 设置一个魔数,用于后续的错误检查或调试。
    }
  }
  
  return(block+1); // 返回分配的内存块的地址。注意这里返回的是block+1,这是因为block实际上指向的是内存块的元数据,而我们需要返回的是可用内存的地址。
}


void *calloc(size_t nelem, size_t elsize) { // 定义一个名为calloc的函数,它接受两个size_t类型的参数nelem和elsize,并返回一个void指针。
  size_t size = nelem * elsize; // 计算需要分配的总内存大小。
  void *ptr = malloc(size); // 调用malloc函数分配内存。
  memset(ptr, 0, size); // 使用memset函数将新分配的内存初始化为0。
  return ptr; // 返回分配的内存的地址。
}

struct block_meta *get_block_ptr(void *ptr) { // 定义一个名为get_block_ptr的函数,它接受一个void指针ptr,并返回一个指向block_meta结构的指针。
  return (struct block_meta*)ptr - 1; // 返回ptr指针前一个位置的地址,这个地址就是内存块的元数据的地址。
}

void free(void *ptr) { // 定义一个名为free的函数,它接受一个void指针ptr。
  if (!ptr) { // 如果ptr为空,
    return; // 则直接返回。
  }

  struct block_meta* block_ptr = get_block_ptr(ptr); // 获取ptr对应的内存块的元数据的地址。
  assert(block_ptr->free == 0); // 断言这个内存块当前是被使用的。
  assert(block_ptr->magic == 0x77777777 || block_ptr->magic == 0x12345678); // 断言这个内存块的魔数是正确的。
  block_ptr->free = 1; // 将这个内存块的状态设置为未使用。
  block_ptr->magic = 0x55555555; // 更新这个内存块的魔数。
}

void *realloc(void *ptr, size_t size) { // 定义一个名为realloc的函数,它接受一个void指针ptr和一个size_t类型的参数size,并返回一个void指针。
  if (!ptr) { 
    return malloc(size); // 如果ptr为空,则realloc函数应该表现得像malloc函数一样。
  }

  struct block_meta* block_ptr = get_block_ptr(ptr); // 获取ptr对应的内存块的元数据的地址。
  if (block_ptr->size >= size) {
    return ptr; // 如果当前内存块的大小已经足够大,那么直接返回ptr。
  }

  void *new_ptr;
  new_ptr = malloc(size); // 分配新的内存空间。
  if (!new_ptr) {
    return NULL; // 如果内存分配失败,返回NULL。
  }
  memcpy(new_ptr, ptr, block_ptr->size); // 将旧内存块中的数据复制到新内存块中。
  free(ptr); // 释放旧内存块。 
  return new_ptr; // 返回新内存块的地址。
}

郭郭大佬的补充代码

考虑free空间的分配
可以加到 free 空间 image.png
如果可以合并就合并 否则会增加 heap 的高度 影响效率

分隔操作

image.png

image.png
线程安全改进
image.png

malloc的可重入性和线程安全分析

malloc函数是一个我们经常使用的函数,如果不对会造成一些潜在的问题。下面就malloc函数的线程安全性和可重入性做一些分析。
我们知道一个函数要做到线程安全,需要解决多个线程调用函数时访问共享资源的冲突。而一个函数要做到可重入,需要不在函数内部使用静态或全局数据,不返回静态或全局数据,也不调用不可重入函数。
malloc函数线程安全但是不可重入的,因为malloc函数在用户空间要自己管理各进程共享的内存链表,由于有共享资源访问,本身会造成线程不安全。为了做到线程安全,需要加锁进行保护。同时这个锁必须是递归锁,因为如果当程序调用malloc函数时收到信号,在信号处理函数里再调用malloc函数,如果使用一般的锁就会造成死锁(信号处理函数中断了原程序的执行),所以要使用递归锁。
虽然使用递归锁能够保证malloc函数的线程安全性,但是不能保证它的可重入性。按上面的场景,程序调用malloc函数时收到信号,在信号处理函数里再调用malloc函数就可能破坏共享的内存链表等资源,因而是不可重入的。
至于malloc函数访问内核的共享数据结构可以正常的加锁保护,因为一个进程程调用malloc函数进入内核时,必须等到返回用户空间前夕才能执行信号处理函数,这时内核数据结构已经访问完成,内核锁已释放,所以不会有问题。

  • 参考 : 【彻底搞懂C指针】Malloc 和 Free 的具体实现_哔哩哔哩_bilibili

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1200409.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

2.如何实现API统一响应-web组件篇

文章目录 1. 统一响应1.1 CommonResult 1. 统一响应 前端调用api接口获得统一的响应&#xff1a; 成功&#xff0c;返回成功的状态码和数据&#xff1b;失败&#xff0c;返回失败的状态码和错误提示。 在标准的 RESTful API 的定义&#xff0c;是推荐使用 HTTP 响应状态码 (…

PEFT概述:最先进的参数高效微调技术

了解参数高效微调技术&#xff0c;如LoRA&#xff0c;如何利用有限的计算资源对大型语言模型进行高效适应。 PEFT概述&#xff1a;最先进的参数高效微调技术 什么是PEFT什么是LoRA用例使用PEFT训练LLMs入门PEFT配置4位量化封装基础Transformer模型保存模型加载模型推理 结论 什…

Module build failed (from ./node_modules/postcss-loader/src/index.js):

出现该错误是你可能没认真看官网的安装配置&#xff0c;可直接看该目录3&#xff0c;一个字一个字看 先安装uview 如果选择v1版本&#xff0c;建议使用npm下载&#xff0c;下面以v1版本为例&#xff0c;使用的是npm下载&#xff0c;导入uview时该文件也在node_modules文件夹里…

常见后缀名总结 为你指点迷津

相信在日常的学习和工作中&#xff0c;大家一定会遇到各种各样的文件类型&#xff0c;他们的后缀名类型各不相同&#xff0c;诸多陌生的文件格式经常让大家不知道他们存在于电脑的意义&#xff0c;想删又没法删&#xff0c;想执行又无法执行。 今天&#xff0c;学长就带领大家一…

Linux学习第40天:Linux SPI 驱动实验(一):乾坤大挪移

Linux版本号4.1.15 芯片I.MX6ULL 大叔学Linux 品人间百味 思文短情长 主从工作方式完成数据交换&#xff0c;形象的说就是武侠中的乾坤大挪移。 本章实验的最终目的就是驱动 I.MX6UALPHA 开发板上的 ICM-20608 这个 SPI 接口的六轴传…

二十五、城市建成区结果制图——复杂图的制作

一、前言 有些时候看到一些参考文献中有些很复杂的图,例如多幅合并在一起,其实这种图本质上就是单一的图合并在一起,然后将其导出即可。 二、具体操作 其实对于制图必备要素的添加就不过多介绍,主要介绍有什么办法保持图形之间一致性,例如,其图例、指北针、比例尺统一…

着实不错的自适应大邻域搜索算法ALNS

文章目录 引言演进路线邻域搜索&#xff0c;NS变邻域搜素&#xff0c;VDNS大邻域搜索&#xff0c;LNS自适应大邻域搜索&#xff0c;ALNS 代码实现34个国内城市的TSP测试集XQF131 相关阅读 引言 之前介绍的差分进化算法和蚁群算法分别适用于求解连续优化问题和组合优化问题&…

Git基本概念和使用方式

Git 是一种版本控制系统&#xff0c;用于管理文件版本的变化。以下是其基本概念和使用方式&#xff1a; 仓库&#xff08;repository&#xff09;&#xff1a;Git 存储代码的地方&#xff0c;可以理解为一个项目的文件夹。提交&#xff08;commit&#xff09;&#xff1a;Git …

【OpenCV实现图像:用OpenCV图像处理技巧之白平衡算法2】

文章目录 概要Gray-world AlgotithmGround Truth Algorithm结论&#xff1a; 概要 随着数字图像处理技术的不断发展&#xff0c;白平衡算法成为了图像处理中一个关键的环节。白平衡的目标是校正图像中的颜色偏差&#xff0c;使得白色在图像中呈现真实的白色&#xff0c;从而提…

Linux之基础开发工具gdb调试器的使用(三)

文章目录 一、Linux调试器-gdb使用1、安装gdb2、背景3、Debug和release4、区分Debug和release 二、Linux调试器-gdb命令演示1、显示指定行之后的代码&#xff08;自动记录最后一条指令&#xff09;2、断点1、打印断点2、查看断点3、删除断点4、使能&#xff08;禁用/开启&#…

统计分钟级别的视频在线用户数+列炸裂+repeat函数

统计分钟级别的视频在线用户数 1、原始数据如下&#xff1a; uid vid starttime endtime select aa as uid,v00l as vid,2023-10-25 12:00 as starttime,2023-10-2512:15 as endtime union select bb as uid,v002 as vid,2023-10-25 12:05 as starttime,2023-10-25 12:19 …

笔记:AI量化策略开发流程-基于BigQuant平台(二)

五、模型训练股票预测 完成了数据处理&#xff0c;接下来就可利用平台集成的各算法进行模型训练和模型预测啦。本文将详细介绍“模型训练”、“模型预测”两大模块操作、原理。 模型训练和模型预测是AI策略区别于传统量化策略的核心&#xff0c;我们通过模型训练模块利用训练…

为什么Android 手机这么慢?如何提高 Android 手机的运行速度

速印机&#xff08;理想、荣大等&#xff09;、复印机&#xff08;夏普、东芝、理光、佳能、震旦等全系列&#xff09;、打印机、扫描仪、传真机、多媒体教学一体机、交互式电子白板、报警器材、监控、竞业达监考设备及其它监考设备、听力考试设备、特种安防设备维护及维修。吴…

Linux必备:这十个流程图让你变的更强!

图是我们与信息联系并处理其重要性的绝佳方法&#xff1b;它们有助于传达关系和抽取信息&#xff0c;并使我们能够可视化概念。 从基本工作流程图到复杂的网络图&#xff0c;组织图&#xff0c;BPMN&#xff08;业务过程模型和符号&#xff09;&#xff0c;UML图等等&#xff0…

面试10000次依然会问的【ThreadLocal】,你还不会?

ThreadLocal简介与基本概念 ThreadLocal&#xff0c;即线程局部变量&#xff0c;是Java语言中用于实现线程数据隔离的一个重要类。这种机制允许在多线程环境中&#xff0c;每个线程都有自己的变量副本&#xff0c;从而使得每个线程都可以独立地改变自己的副本&#xff0c;而不…

JDK1.8 新特性(一)【默认方法、静态方法和Lambda表达式】

前言 今天学习Java8 新特性&#xff0c;主要是之前在学习 Scala、JavaFX 中遇到一些 Lambda 表达式&#xff0c;感觉 lambda 表达式确实很简洁&#xff0c;很有必要学一学。 目录 前言 1、接口的默认方法与静态方法 编写接口 编写接口的实现类 测试 2、Lambda表达式&am…

YOLO目标检测——交通标志分类数据集下载分享【含对应voc、coco和yolo三种格式标签】

实际项目应用&#xff1a;交通标志识别数据集在自动驾驶、交通安全监控、智能交通系统、驾驶员辅助系统和城市规划等领域都有广泛应用的潜力数据集说明&#xff1a;交通标志分类数据集&#xff0c;真实场景的高质量图片数据&#xff0c;数据场景丰富&#xff0c;含多场景白天黑…

Linux之gdb

gdb就是一个Linux的调试工具&#xff0c;类似与vs里面的调试 可执行程序也有格式&#xff0c;不是简单的二进制堆砌

PyCharm因安装了illuminated Cloud插件导致加载项目失败

打开Pycharm时会有弹窗提示&#xff1a; The license for Illuminated Cloud is invalid or has expired. All Illuminated Cloud features will be disabled. 这个弹窗会导致你加载项目一直失败&#xff0c;close project 也关不掉&#xff0c;我都是用任务管理器杀死进程的…

DevChat全能型AI编程助手,助你“以一敌三卷翻好友”

DevChat全能型AI编程助手&#xff0c;助你“以一敌三卷翻好友” 什么是DevChat&#xff0c;它能帮助我们做什么&#xff1f; DevChat是OpenAI的一个产品&#xff0c;它是一个可以进行编程相关对话的AI。这意味着你可以使用它来解决一些编程上的问题或者获取关于编程的建议。 …