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

【彻底搞懂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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

FD-Align论文阅读

FD-Align: Feature Discrimination Alignment for Fine-tuning Pre-Trained Models in Few-Shot Learning&#xff08;NeurIPS 2023&#xff09; 主要工作是针对微调的和之前的prompt tuining&#xff0c;adapter系列对比 Motivation&#xff1a; 通过模型对虚假关联性的鲁棒…

windows系统winget一键安装和使用

winget命令概述 用户可以在 Windows 10 和 Windows 11 计算机上使用 winget 命令行工具来发现、安装、升级、删除和配置应用程序。 此工具是 Windows 程序包管理器服务的客户端接口 在 Windows 沙盒上安装 winget Windows 沙盒提供了一个轻型桌面环境&#xff0c;可以安全地独…

【数字图像处理】RGB 转灰度图

常见的数字图像格式有 RGB, RGBA, YCbCr 等&#xff0c;RGB/RGBA 格式适合存储&#xff0c;而 YCbCr 格式适合图像处理。在数字图像处理中&#xff0c;通常需要将 RGB 格式的图像变换为灰度图&#xff0c;再进行后续的处理&#xff0c;例如边缘检测、锐化等。本文主要介绍数字图…

改进YOLO系列 | YOLOv5/v7 引入反向残差注意力模块 iRMB | 《ICCV 2023 最新论文》

论文地址:https://arxiv.org/abs/2301.01146 代码地址:https://github.com/zhangzjn/EMO 本论文着重于开发现代、高效、轻量级的模型,用于进行密集预测,同时在参数、FLOPs和性能之间进行权衡。倒置残差块(IRB)作为轻量级CNN的基础设施,但在基于注意力的研究中尚未找到对…

计算机毕业设计选题推荐-个人记账理财微信小程序/安卓APP-项目实战

✨作者主页&#xff1a;IT毕设梦工厂✨ 个人简介&#xff1a;曾从事计算机专业培训教学&#xff0c;擅长Java、Python、微信小程序、Golang、安卓Android等项目实战。接项目定制开发、代码讲解、答辩教学、文档编写、降重等。 ☑文末获取源码☑ 精彩专栏推荐⬇⬇⬇ Java项目 Py…

Linux——vim简介、配置方案(附带超美观的配置方案)、常用模式的基本操作

vim简介、配置方案、常用模式的基本操作 本章思维导图&#xff1a; 注&#xff1a;本章思维导图对应的xmind和.png文件都已同步导入至资源 1. vim简介 vim是Linux常用的文本编辑器&#xff0c;每个Linux账户都独有一个vim编辑器 本篇我们介绍vim最常用的三种模式&#xff1a;…

6可靠的局域网组建

前面聊的拓扑结构都比较简单&#xff0c;所以能用&#xff0c;但是未必可靠。为了可靠&#xff0c;我们需要做冗余&#xff0c;同时需要做一些其他的配置。 生成树协议STP 假设交换机按照上面的方案连&#xff0c;虽然可以提高网络可靠性&#xff0c;但是因为形成了环路&#…

谈谈越来越无效的拥塞控制

简单看一个图&#xff1a; 它不是互联网本身&#xff0c;但这是典型网络的必要组件&#xff0c;它决定了 flow 如何从从一边流向另一边&#xff1a;一条 flow 经过交换节点通过 NIC 被导入一条链路前在 buffer 中排队。 现如今大多数工程师的工作都在折腾那个单独的盒子&…

Django(复习篇)

项目创建 1. 虚拟环境 python -m venv my_env ​ cd my_env activate/deactivate ​ pip install django ​2. 项目和app创建 cd mypros django-admin startproject Pro1 django-admin startapp app1 ​3. settings配置INSTALLED_APPS【app1"】TEMPLATES【 DIRS: [os.pat…

双11终极官方战报 凯迪仕智能锁全网全渠道第一 持续领跑智能锁行业

一年一度双11狂欢盛典暂时落下帷幕&#xff0c;作为下半年最大的电商购物节&#xff0c;本次双11电商平台借机推出优惠券、补贴、折扣等促销活动&#xff0c;激发消费者购物热情。其中&#xff0c;智能门锁等智能家居产品更是取得了亮眼的成交。 据悉&#xff0c;凯迪仕智能锁双…

【Linux】 mdir命令使用

mdir 为mtools工具指令&#xff0c;模拟MS-DOS的dir指令&#xff0c;可显示MS-DOS文件系统中的目录内容。 语法 mdir [参数][目录] mdir命令 -Linux手册页 命令选项及作用 执行令 mdir--help 执行命令结果 参数 -a  显示隐藏文件。-f  不显示磁盘所剩余的可用空间。-w…

【C语言】函数的系统化精讲(三)

文章目录 一、递归举例二、递归举例2.1求n的阶乘2.2 顺序打印⼀个整数的每⼀位 三、递归与迭代3.1递归的思考3.2求第n个斐波那契数 总结 一、递归举例 .通过上回&#xff08;【C语言】函数的系统化精讲&#xff08;二&#xff09;&#xff09;我们了解到递归的限制条件&#x…

Java终端模式小尝试

Java终端模式小尝试 1、IDE中终端1.1 拉去代码 jediterm1.2 IDE调用系统终端 2、待续~~ 1、IDE中终端 终端_Intellij IDEA、Terminal emulator | pycharm Documentation JetBrains jediterm WindTerm&#xff1a;新一代开源免费的终端工具&#xff0c;GitHub星标6.6k&#xff…

冒泡排序

贵阳这个地方的天气变化好大呀&#xff0c;前两天晒大太阳&#xff0c;今天就冷的脚抖&#xff0c;简直不要太冷&#xff0c;但是不管怎么样&#xff0c;还是要学习的哟&#xff01; 冬天来了&#xff0c;春天确实还有一点远&#xff01; 好了&#xff0c;话不多说&#xff0c;…

linux_day03

1、复习 遇到虚拟机异常退出&#xff0c;会生成配置文件&#xff0c;不确定文件以后是不是还要用的情况下&#xff0c;先改文件名&#xff0c;再启动虚拟机&#xff1b; 2、磁盘相关命令&#xff1a; df&#xff08;disk full&#xff09;&#xff1a;查看磁盘整体状况 -h &…

ztree结合hmap使用经验分享

项目背景 在建德封控拦截系统&#xff08;Vue3antd2.x&#xff09;为追求更快的地图初始化体验&#xff0c;在尝试了hmap2.5.0版本以及2.6.3版本后&#xff0c;由于这两个版本在现场电脑的初始化速度不够流畅&#xff0c;最终使用的是hmap2.1.3版本。同时由于布控选设备&#…

2023年【起重机械指挥】考试试卷及起重机械指挥操作证考试

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 2023年起重机械指挥考试试卷为正在备考起重机械指挥操作证的学员准备的理论考试专题&#xff0c;每个月更新的起重机械指挥操作证考试祝您顺利通过起重机械指挥考试。 1、【多选题】《中华人民共和国特种设备安全法》…

在CMake中打印日志信息

message([STATUS|WARNING|AUTHOR_WARNING|FATAL_ERROR|SEND_ERROR] "message to display" ...) (无) &#xff1a;重要消息 STATUS &#xff1a;非重要消息 WARNING&#xff1a;CMake 警告, 会继续执行 AUTHOR_WARNING&#xff1a;CMake 警告 (dev), 会继续执行 SEN…

【hacker送书第一期】嵌入式虚拟化技术与应用

第一期图书推荐 前言为什么嵌入式系统需要虚拟化技术&#xff1f;专家推荐本书适用群体内容简介目录权威作者团队参与方式 前言 随着物联网设备的爆炸式增长和万物互联应用的快速发展&#xff0c;虚拟化技术在嵌入式系统上受到了业界越来越多的关注、重视和实际应用。嵌入式系…

云端部署ChatGLM-6B

大模型这里更新是挺快的&#xff0c;我参考的视频教程就和我这个稍微有些不一样&#xff0c;这距离教程发布只过去4天而已… 不过基本操作也差不多 AutoDL算力云&#xff1a;https://www.autodl.com/home ChatGLM3&#xff1a;https://github.com/THUDM/ChatGLM3/tree/main Hug…