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

news2024/12/23 16:16:07

【彻底搞懂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/1199847.html

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

相关文章

软件架构的可维护性指标——代码圈复杂度

代码圈复杂度 1、目的2、前言3、简介4、案例5、降低6、插件7、总结 1、目的 区别于常规的高内聚、低耦合、抽象、封装这种定性的指标&#xff0c;我想通过对软件架构可维护性的可量化的指标的分享&#xff0c;帮助大家在日常的开发工作中&#xff0c;有一个更为广阔的视角去审…

No181.精选前端面试题,享受每天的挑战和学习

🤍 前端开发工程师(主业)、技术博主(副业)、已过CET6 🍨 阿珊和她的猫_CSDN个人主页 🕠 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 🍚 蓝桥云课签约作者、已在蓝桥云课上架的前后端实战课程《Vue.js 和 Egg.js 开发企业级健康管理项目》、《带你从入…

共享内存原理和实现

实现原理 实现函数 1&#xff0c;ftok--shmget--shmat--shmdt shmget用于分配映射物理内存的虚拟内存。 怎么保证不同进程访问同一块物理内存呢 key_t ftok(const char *pathname, int proj_id); ftok的第一个参数是一个文件&#xff0c;只要使用同一个文件进行映射&#x…

STM32F4之看门狗

1、 看门狗作用 单片机复位的方式&#xff1a;硬件复位 -- reset按键 上电复位 -- 电容 看门狗复位 看门狗的复位功能主要是用于一些平常难以操作的场合去帮助我们进行复位操作。当你单片机突然死机或者程序跑飞了&#xff0c;看门狗就可以检测得到并且及时帮你复位。看门狗也可…

74hc595模块参考

74hc595模块参考 8位串行并行输出&#xff08;SIPO&#xff09;移位寄存器 使用74HC595移位寄存器扩展微控制器上的输出引脚数量。如果你需要扩充输入引脚的数量那么你需要74HC165移位寄存器。 SER&#xff08;串行输入&#xff09;引脚用于一次一位地将数据发送到移位寄存器…

(离散数学)逻辑连接词

异或可以理解为不同为1相同为0 P->Q的前件和后件满足0->1的其中一个就为真 <—>可以看做 &#xff0c;相同为1不同为0 异或与等价相反

Torch Hub 系列#2:VGG 和 ResNet

一、说明 在上一篇教程中,我们了解了 Torch Hub 背后的本质及其概念。然后,我们使用 Torch Hub 的复杂性发布了我们的模型,并通过相同的方式访问它。但是,当我们的工作要求我们利用 Torch Hub 上提供的众多全能模型之一时,会发生什么? 在本教程中,我们将学习如何利用称为…

MySQL:日志系统

目录 概述错误日志&#xff08;error log&#xff09;慢查询日志&#xff08;slow query log&#xff09;一般查询日志( general log )中继日志&#xff08;relay log&#xff09;Buffer Pool 缓存回滚日志&#xff08;undo log)概述undo log 作用undo log 的存储机制Undo log …

WorkPlus Meet:局域网内部使用的高效视频会议系统

随着全球化和远程办公的趋势&#xff0c;视频会议已成为现代企业和机构不可或缺的沟通工具。而现在&#xff0c;大多数政企单位或者涉密强的企业&#xff0c;都会使用局域网部署的音视频会议系统&#xff0c;提供更高的安全性和隐私保护。因为音视频会议中可能涉及到公司机密和…

Angular 使用教程——基本语法和双向数据绑定

Angular 是一个应用设计框架与开发平台&#xff0c;旨在创建高效而精致的单页面应用 Angular 是一个基于 TypeScript 构建的开发平台。它包括&#xff1a;一个基于组件的框架&#xff0c;用于构建可伸缩的 Web 应用&#xff0c;一组完美集成的库&#xff0c;涵盖各种功能&…

基于SSM的考研图书电子商务平台的设计与实现

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;Vue 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#xff1a;是 目录…

HDMI之编码篇

概述 HDMI 2.0b(含)以下版本,采用3个Channel方式输出。传输又分为3三种周期,视频数据,数据岛以及控制周期。视频传输采用8/10编码。数据岛采用4/10编码(TERC4)。控制周期采用2/10。编码都拓展成了10bits。 上图中,Pixel component(e.g.B)->D[7:0]表示视频数据周期…

linux之IPC

linux之IPC 什么是IPC共享内存(shm)ftokshmgetshmatshmdtshmctl 消息队列msggetmsgrcvmsgsndmsgctl 旗语(信号量)semgetsemctlsemopsem三级标题三级标题 ipc命令守护进程查看守护进程 什么是IPC IPC: Inter(内核) Process(进程) Communicton&#xff08;通信&#xff09; 共享内…

【Delphi】 各个平台使用 ntfy 效果说明

目录 一、Delphi 中使用 ntfy 库下载地址 二、各个平台使用效果说明 1. android 平台 2. ios 平台 3. windows 平台 三、总结 一、Delphi 中使用 ntfy 库下载地址 官方的文档地址&#xff1a;ntfyDelphi 接口库地址&#xff1a;GitHub - hazzelnuts/ntfy-for-delphi at …

冯·诺依曼结构

一、约翰冯诺依曼---计算机之父 约翰冯诺依曼&#xff08;John von Neumann&#xff0c;1903年12月28日—1957年2月8日&#xff09;&#xff0c;出生于匈牙利布达佩斯&#xff0c;匈牙利裔美籍数学家、计算机科学家、物理学家和化学家&#xff0c;美国国家科学院院士&#xff…

OpenCV:图像旋转与缩放

人工智能的学习之路非常漫长&#xff0c;不少人因为学习路线不对或者学习内容不够专业而举步难行。不过别担心&#xff0c;我为大家整理了一份600多G的学习资源&#xff0c;基本上涵盖了人工智能学习的所有内容。点击下方链接,0元进群领取学习资源,让你的学习之路更加顺畅!记得…

Java Web——TomcatWeb服务器

目录 1. 服务器概述 1.1. 服务器硬件 1.2. 服务器软件 2. Web服务器 2.1. Tomcat服务器 2.2. 简单的Web服务器使用 1. 服务器概述 服务器指的是网络环境下为客户机提供某种服务的专用计算机&#xff0c;服务器安装有网络操作系统和各种服务器的应用系统服务器的具有高速…

linux入门---自旋锁和读写锁

自旋锁 首先通过一个例子来带着大家理解自旋锁&#xff0c;在生活中大家肯定都等过人比如你们一家人准备出去玩可是出发的时候妻子发现自己还没有化妆于是连忙赶回了家这个时候其他人就得在楼下等着&#xff0c;但是这个等又分为两种情况第一种是真的在楼下等其他的什么事都没…

Nginx:不同域名访问同一台机器的不同项目

Nginx很简单就可以解决同一台机器同时跑两个或者多个项目&#xff0c;而且都通过域名从80端口走。 以Windows环境下nginx服务为例&#xff0c;配置文件nginx.conf中&#xff0c;http中加上 include /setup/nginx-1.20.1/conf/conf.d/*.conf;删除server部分&#xff0c;完整如…

RT-Thread Studio开发 新手入门

文章目录 前言一、RT-Thread Studio 与 STM32CubeMX 下载安装二、新建工程三、点亮LED灯四、按键中断五、串口通信六、OLED显示 前言 软件开发环境&#xff1a;RT-Thread Studio、STM32CubeMX 硬件&#xff1a;STM32F407ZGT6 一、RT-Thread Studio 与 STM32CubeMX 下载安装 …