当前位置: 首页 > article >正文

malloc 是如何分配内存的?——C 语言内存分配详解

文章目录

  • malloc是如何分配内存的?——C语言内存分配详解
    • 一、引言
    • 二、内存分配的基本概念
      • 1. 虚拟内存与物理内存
      • 2. 进程内存布局
    • 三、malloc函数详解
      • 1. 函数原型与功能
      • 2. 关键特性
    • 四、malloc的底层实现机制
      • 1. 内存分配器的角色
      • 2. 分配策略
      • 3. 内存碎片问题
    • 五、glibc中的malloc实现(ptmalloc2)
      • 1. 内存池结构
      • 2. 分配流程
      • 3. 空闲块管理
      • 4. 性能优化
    • 六、malloc的常见问题与解决方案
      • 1. 内存泄漏(Memory Leak)
      • 2. 野指针(Dangling Pointer)
      • 3. 双重释放(Double Free)
      • 4. 缓冲区溢出(Buffer Overflow)
    • 七、malloc与其他内存分配函数的对比
      • 1. malloc vs calloc
      • 2. malloc vs realloc
      • 3. malloc vs alloca
    • 八、自定义内存分配器示例
    • 九、总结

malloc是如何分配内存的?——C语言内存分配详解

一、引言

在C语言编程中,malloc函数是动态内存分配的核心工具之一。它允许程序在运行时请求内存,这对于处理动态数据结构(如链表、树和数组)至关重要。但你是否想过,当我们调用malloc(1024)时,操作系统究竟做了什么?内存是如何被分配和管理的?本文将深入探讨malloc的工作原理,从底层机制到实际应用,帮助你全面理解C语言的内存分配系统。

二、内存分配的基本概念

1. 虚拟内存与物理内存

现代操作系统使用虚拟内存技术,为每个进程提供独立的地址空间。虚拟内存与物理内存通过页表(Page Table)映射,使得:

  • 每个进程认为自己拥有连续的、独占的内存空间
  • 操作系统可以更灵活地管理物理内存,实现内存保护和共享

2. 进程内存布局

一个典型的C程序内存布局包含以下区域:

  • 代码段(Text Segment):存储程序的机器指令
  • 数据段(Data Segment):存储已初始化的全局变量和静态变量
  • BSS段(BSS Segment):存储未初始化的全局变量和静态变量
  • 堆(Heap):动态分配的内存区域,向上增长(从低地址到高地址)
  • 栈(Stack):存储函数调用信息和局部变量,向下增长(从高地址到低地址)

三、malloc函数详解

1. 函数原型与功能

#include <stdlib.h>void* malloc(size_t size);
  • 功能:分配指定大小(以字节为单位)的内存块,返回指向该内存块的指针
  • 返回值
    • 成功时返回分配内存的起始地址
    • 失败时返回NULL(通常表示内存不足)

2. 关键特性

  • 内存未初始化malloc分配的内存内容是未定义的,使用前需要初始化
  • 连续内存:分配的内存块是连续的,适合存储数组等需要连续空间的数据结构
  • 对齐要求:分配的内存地址通常是系统字长的整数倍,以提高访问效率

四、malloc的底层实现机制

1. 内存分配器的角色

malloc是C标准库提供的内存分配函数,其实现依赖于操作系统提供的内存管理机制。在Linux系统中,主要通过以下两个系统调用实现内存分配:

  • brk/sbrk:调整堆的边界(break指针)
  • mmap:将文件或设备映射到内存,也可用于分配匿名内存

2. 分配策略

内存分配器通常采用以下策略:

  • 空闲块管理:维护一个空闲内存块链表,记录可用内存块的位置和大小
  • 首次适应(First Fit):找到第一个足够大的空闲块分配
  • 最佳适应(Best Fit):找到最接近请求大小的空闲块分配
  • 最差适应(Worst Fit):找到最大的空闲块分配,分割后剩余部分仍较大

3. 内存碎片问题

频繁的内存分配和释放会导致两种碎片:

  • 外部碎片:空闲内存被分割成多个小片段,无法满足大内存请求
  • 内部碎片:分配的内存块比实际请求的大,造成空间浪费

现代内存分配器通过以下方式减少碎片:

  • 合并相邻的空闲块(边界标记法)
  • 分级分配策略(小内存块和大内存块采用不同的分配方式)

五、glibc中的malloc实现(ptmalloc2)

GNU C Library(glibc)中的malloc实现称为ptmalloc2,采用了复杂而高效的内存管理策略:

1. 内存池结构

ptmalloc2使用线程缓存(Thread Cache)和主分配区(Main Arena)的分层结构:

  • 线程缓存(TCache):每个线程独立的小型内存池,用于快速分配小内存块(默认<=256字节)
  • 主分配区(Main Arena):全局分配区,处理跨线程的内存请求
  • 非主分配区(Non-Main Arena):每个线程可拥有自己的分配区,减少锁竞争

2. 分配流程

  1. 小内存分配(<=256字节)

    • 优先从线程缓存(TCache)中分配
    • 若TCache为空,则从主分配区或非主分配区获取一批内存块
  2. 中等内存分配(256字节~128KB)

    • 从主分配区或非主分配区的空闲列表中查找合适的块
    • 若没有足够大的块,通过sbrk扩展堆
  3. 大内存分配(>128KB)

    • 直接使用mmap分配匿名内存,不经过堆管理器
    • 释放时直接通过munmap归还操作系统

3. 空闲块管理

ptmalloc2使用多种空闲列表管理不同大小的内存块:

  • fast bins:快速分配小内存块(默认<=64字节),不合并相邻空闲块
  • small bins:处理小内存块(64字节~512字节),采用FIFO队列
  • large bins:处理大内存块(>512字节),按大小分组的有序列表
  • unsorted bin:临时存放释放的内存块,在下次分配时进行整理

4. 性能优化

ptmalloc2通过以下方式提高性能:

  • 线程局部存储(TLS):减少线程间锁竞争
  • 内存预分配:一次从操作系统获取较大内存块,减少系统调用次数
  • 内存对齐:确保分配的内存地址满足硬件对齐要求

六、malloc的常见问题与解决方案

1. 内存泄漏(Memory Leak)

问题:分配的内存未被释放,导致可用内存逐渐减少
解决方案

  • 使用free释放不再使用的内存
  • 遵循"谁分配,谁释放"的原则
  • 使用工具检测内存泄漏(如Valgrind)

2. 野指针(Dangling Pointer)

问题:指针指向已释放的内存
解决方案

  • 释放内存后立即将指针置为NULL
  • 避免返回局部变量的地址

3. 双重释放(Double Free)

问题:同一内存块被释放多次
解决方案

  • 确保每个内存块只被释放一次
  • 使用智能指针模式(如引用计数)

4. 缓冲区溢出(Buffer Overflow)

问题:写入数据超过分配的内存边界
解决方案

  • 始终检查数据长度
  • 使用安全的字符串处理函数(如strncpy代替strcpy

七、malloc与其他内存分配函数的对比

1. malloc vs calloc

void* malloc(size_t size);
void* calloc(size_t num, size_t size);
  • malloc:只分配内存,不初始化
  • calloc:分配内存并初始化为0
  • 性能calloc通常比malloc慢,因为需要额外的初始化操作

2. malloc vs realloc

void* malloc(size_t size);
void* realloc(void* ptr, size_t new_size);
  • malloc:分配新的内存块
  • realloc:调整已分配内存块的大小
    • 若原内存块后有足够空间,直接扩展
    • 否则分配新内存块,复制数据,释放原内存块

3. malloc vs alloca

void* malloc(size_t size);
void* alloca(size_t size);
  • malloc:在堆上分配内存,需手动释放
  • alloca:在栈上分配内存,函数返回时自动释放
  • 风险alloca可能导致栈溢出,使用需谨慎

八、自定义内存分配器示例

下面是一个简化版的内存分配器实现,演示基本的内存分配原理:

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>// 内存块头部结构
typedef struct MemBlock {size_t size;      // 内存块大小(不包含头部)bool is_free;     // 是否空闲struct MemBlock* next;  // 指向下一个内存块
} MemBlock;// 全局内存池和头部指针
static void* memory_pool = NULL;
static MemBlock* head = NULL;
static size_t total_size = 0;// 初始化内存池
void my_malloc_init(size_t size) {// 分配大块内存memory_pool = malloc(size);if (!memory_pool) {fprintf(stderr, "Memory allocation failed\n");exit(EXIT_FAILURE);}// 初始化第一个内存块head = (MemBlock*)memory_pool;head->size = size - sizeof(MemBlock);head->is_free = true;head->next = NULL;total_size = size;
}// 分配内存
void* my_malloc(size_t size) {if (!memory_pool) {my_malloc_init(1024 * 1024);  // 默认1MB内存池}MemBlock* current = head;MemBlock* best_fit = NULL;// 查找最佳匹配的空闲块while (current) {if (current->is_free && current->size >= size) {if (!best_fit || current->size < best_fit->size) {best_fit = current;}}current = current->next;}// 没有找到合适的空闲块if (!best_fit) {return NULL;}// 如果剩余空间足够大,分割内存块if (best_fit->size > size + sizeof(MemBlock)) {MemBlock* new_block = (MemBlock*)((char*)best_fit + sizeof(MemBlock) + size);new_block->size = best_fit->size - size - sizeof(MemBlock);new_block->is_free = true;new_block->next = best_fit->next;best_fit->size = size;best_fit->next = new_block;}best_fit->is_free = false;return (void*)((char*)best_fit + sizeof(MemBlock));
}// 释放内存
void my_free(void* ptr) {if (!ptr) return;// 获取内存块头部MemBlock* block = (MemBlock*)((char*)ptr - sizeof(MemBlock));block->is_free = true;// 合并相邻的空闲块MemBlock* current = head;while (current && current->next) {if (current->is_free && current->next->is_free) {// 合并当前块和下一个块current->size += sizeof(MemBlock) + current->next->size;current->next = current->next->next;} else {current = current->next;}}
}// 示例用法
int main() {my_malloc_init(1024);  // 初始化1KB内存池int* ptr1 = (int*)my_malloc(sizeof(int));*ptr1 = 42;char* ptr2 = (char*)my_malloc(10);snprintf(ptr2, 10, "hello");my_free(ptr1);my_free(ptr2);return 0;
}

九、总结

malloc作为C语言中最基本的内存分配函数,背后涉及复杂的内存管理机制。通过本文的介绍,我们了解到:

  1. 内存分配原理:虚拟内存、进程内存布局和系统调用
  2. malloc实现细节:空闲块管理、分配策略和碎片处理
  3. 常见问题与解决方案:内存泄漏、野指针和缓冲区溢出
  4. 相关函数对比malloccallocreallocalloca的区别

理解malloc的工作原理不仅有助于编写高效、安全的C代码,还能为学习更高级的内存管理技术(如智能指针、垃圾回收)打下基础。在实际开发中,建议结合内存分析工具(如Valgrind、AddressSanitizer)来检测和修复内存相关问题,提高代码质量和稳定性。

相关文章:

malloc 是如何分配内存的?——C 语言内存分配详解

文章目录 malloc是如何分配内存的&#xff1f;——C语言内存分配详解一、引言二、内存分配的基本概念1. 虚拟内存与物理内存2. 进程内存布局 三、malloc函数详解1. 函数原型与功能2. 关键特性 四、malloc的底层实现机制1. 内存分配器的角色2. 分配策略3. 内存碎片问题 五、glib…...

Opencl

**OpenCL&#xff08;Open Computing Language&#xff09;**是一种用于异构平台&#xff08;包括CPU、GPU、FPGA、DSP等&#xff09;上的并行计算框架和编程标准。它由Khronos Group制定&#xff0c;旨在提供一种跨平台、统一的编程接口&#xff0c;使开发者可以利用不同硬件设…...

如何在 HTML 中添加按钮

原文&#xff1a;如何在 HTML 中添加按钮 | w3cschool笔记 &#xff08;请勿将文章标记为付费&#xff01;&#xff01;&#xff01;&#xff01;&#xff09; 在网页开发中&#xff0c;按钮是用户界面中不可或缺的元素之一。无论是用于提交表单、触发动作还是导航&#xff0…...

【优秀三方库研读】quill 开源库中的命名空间为什么要用宏封装

将命名空间封装成宏的作用与优势 QUILL_BEGIN_NAMESPACE 和 QUILL_END_NAMESPACE 这种宏封装是 C++ 库开发中的常见技巧,主要解决以下问题并提供显著优势: 1. 解决核心问题:命名空间嵌套与版本控制 问题场景: 库需要支持多版本共存(如 quill::v1, quill::v2),但希望默认…...

AlphaFold3运行错误及解决方法(1)

1. chemical_component_sets.pickle 运行alphafold3遇到下面的问题: FileNotFoundError: [Errno 2] No such file or directory: /xxx/xxx/anaconda3/envs/alphafold3/lib/python3.11/site-packages/alphafold3/constants/converters/chemical_component_sets.pickle搜索你的系…...

Linux--进程的程序替换

问题导入&#xff1a; 前面我们知道了&#xff0c;fork之后&#xff0c;子进程会继承父进程的代码和“数据”&#xff08;写实拷贝&#xff09;。 那么如果我们需要子进程完全去完成一个自己的程序怎么办呢&#xff1f; 进程的程序替换来完成这个功能&#xff01; 1.替换原理…...

调教 DeepSeek - 输出精致的 HTML MARKDOWN

【序言】 不知道是不是我闲的蛋疼&#xff0c;对百度AI 和 DeepSeek 的回答都不太满意。 DeepSeek 回答句子的引用链接&#xff0c;始终无法准确定位。有时链接只是一个域名&#xff0c;有时它给的链接是搜索串如: baidu.com/?q"搜索内容"。 百度AI 回答句子的引用…...

【笔记】Windows系统部署suna基于 MSYS2的Poetry 虚拟环境backedn后端包编译失败处理

基于 MSYS2&#xff08;MINGW64&#xff09;中 Python 的 Poetry 虚拟环境包编译失败处理笔记 一、背景 在基于 MSYS2&#xff08;MINGW64&#xff09;中 Python 创建的 Poetry 虚拟环境里&#xff0c;安装 Suna 开源项目相关包时编译失败&#xff0c;阻碍项目正常部署。 后端…...

GQA(Grouped Query Attention):分组注意力机制的原理与实践《一》

GQA&#xff08;Grouped Query Attention&#xff09;是近年来在大语言模型中广泛应用的一种注意力机制优化方法&#xff0c;最初由 Google 在 2023 年提出。它是对 Multi-Query Attention (MQA) 的扩展&#xff0c;旨在平衡模型性能与计算效率。 &#x1f31f; GQA 是什么&…...

【深度学习优化算法】02:凸性

【作者主页】Francek Chen 【专栏介绍】 ⌈ ⌈ ⌈PyTorch深度学习 ⌋ ⌋ ⌋ 深度学习 (DL, Deep Learning) 特指基于深层神经网络模型和方法的机器学习。它是在统计机器学习、人工神经网络等算法模型基础上&#xff0c;结合当代大数据和大算力的发展而发展出来的。深度学习最重…...

JAVA国际版一对一视频交友视频聊天系统源码支持H5+APP

全球畅连无界社交&#xff1a;JAVA国际版一对一视频交友系统源码&#xff08;H5APP双端覆盖&#xff09; 在全球化社交需求激增的今天&#xff0c;构建一个支持多语言、适配国际支付且功能丰富的视频交友平台&#xff0c;成为出海创业者和企业的核心诉求。JAVA国际版一对一视频…...

策略公开了:年化494%,夏普比率5.86,最大回撤7% | 大模型查询akshare,附代码

原创内容第907篇&#xff0c;专注智能量化投资、个人成长与财富自由。 这位兄弟的策略公开了&#xff0c;年化494%&#xff0c;夏普比率5.86&#xff0c;最大回撤7%&#xff0c;欢迎大家前往围观&#xff1a; http://www.ailabx.com/strategy/683ed10bdabe146c4c0b2293 系统代…...

【C++】string类的模拟实现(详解)

文章目录 上文链接一、整体框架二、构造函数1. default2. copy3. range 三、析构函数四、拷贝构造(1) 传统写法(2) 现代写法 五、赋值重载(1) 传统写法(2) 现代写法 六、获取元素1. operator[ ] 七、迭代器1. begin2. end 八、容量相关1. size2. reserve3. clear 九、修改操作1…...

业界宽松内存模型的不统一而导致的软件问题, gcc, linux kernel, JVM

当不同CPU厂商未能就统一的宽松内存模型&#xff08;Relaxed Memory Model&#xff09;达成一致&#xff0c;很多软件的可移植性会收到限制或损害&#xff0c;主要体现在以下几个方面&#xff1a; 1. 可能的理论限制 1.1. 并发程序的行为不一致 现象上&#xff0c;同一段多线程…...

多模态大语言模型arxiv论文略读(101)

ML-Mamba: Efficient Multi-Modal Large Language Model Utilizing Mamba-2 ➡️ 论文标题&#xff1a;ML-Mamba: Efficient Multi-Modal Large Language Model Utilizing Mamba-2 ➡️ 论文作者&#xff1a;Wenjun Huang, Jiakai Pan, Jiahao Tang, Yanyu Ding, Yifei Xing, …...

量化Quantization初步之--带量化(QAT)的XOR异或pyTorch版250501

量化(Quantization)这词儿听着玄&#xff0c;经常和量化交易Quantitative Trading (量化交易)混淆。 其实机器学习(深度学习)领域的量化Quantization是和节约内存、提高运算效率相关的概念&#xff08;因大模型的普及&#xff0c;这个量化问题尤为迫切&#xff09;。 揭秘机器…...

Linux Maven Install

在 CentOS&#xff08;例如 CentOS 7 或 CentOS 8&#xff09;中安装 Maven&#xff08;Apache Maven&#xff09;的方法主要有两种&#xff1a;使用包管理器&#xff08;简单但可能版本较旧&#xff09;&#xff0c;或者手动安装&#xff08;推荐&#xff0c;可获得最新版&…...

#Java篇:学习node后端之sql常用操作

学习路线 1、javascript基础&#xff1b; 2、nodejs核心模块 fs: 文件系统操作 path: 路径处理 http / https: 创建服务器或发起请求 events: 事件机制&#xff08;EventEmitter&#xff09; stream: 流式数据处理 buffer: 处理二进制数据 os: 获取操作系统信息 util: 工具方…...

电网“逆流”怎么办?如何实现分布式光伏发电全部自发自用?

2024年10月9日&#xff0c;国家能源局综合司发布了《分布式光伏发电开发建设管理办法&#xff08;征求意见稿&#xff09;》&#xff0c;意见稿规定了户用分布式光伏、一般工商业分布式光伏以及大型工商业分布式光伏的发电上网模式&#xff0c;当选择全部自发自用模式时&#x…...

如何查看电脑电池性能

检查电脑电池性能的方法如下&#xff1a; 按下winR键&#xff0c;输入cmd回车&#xff0c;进入命令行窗口 在命令行窗口输入powercfg /batteryreport 桌面双击此电脑&#xff0c;把刚刚复制的路径粘贴到文件路径栏&#xff0c;然后回车 回车后会自动用浏览器打开该报告 红…...

kubernetes》》k8s》》kubectl proxy 命令后面加一个

命令后面加一个& 在Linux终端中&#xff0c;如果在命令的末尾加上一个&符号&#xff0c;这表示将这个任务放到后台去执行 kubectl proxy 官网资料 是 Kubernetes 提供的一个命令行工具&#xff0c;用于在本地和 Kubernetes API Server 之间创建一个安全的代理通道。…...

深入理解Linux系统进程切换

目录 引言 一、什么是进程切换&#xff1f; 二、进程切换的触发条件 三、进程切换的详细步骤 1、保存当前进程上下文&#xff1a; 2、更新进程控制块(PCB)&#xff1a; 3、选择下一个进程&#xff1a; 4、恢复新进程上下文&#xff1a; 5、切换地址空间&#xff1a; 6…...

网络安全运维实训室建设方案

一、网络安全运维人才需求与实训困境 在数字化时代&#xff0c;网络安全已成为国家安全、社会稳定和经济发展的重要基石。随着信息技术的飞速发展&#xff0c;网络安全威胁日益复杂多样&#xff0c;从个人隐私泄露到企业商业机密被盗&#xff0c;从关键基础设施遭受攻击到社会…...

DBeaver 连接mysql报错:CLIENT_PLUGIN_AUTH is required

DBeaver 连接mysql报错&#xff1a;CLIENT_PLUGIN_AUTH is required 一、必须要看这个 >> &#xff1a;参考文献 二、补充 2.1 说明 MySQL5、6这些版本比较老&#xff0c;而DBeaver默认下载的是MySQL8的连接库&#xff0c;所以连接旧版本mysql报错&#xff1a;CLIEN…...

联通专线赋能,亿林网络裸金属服务器:中小企业 IT 架构升级优选方案

在当今数字化飞速发展的时代&#xff0c;中小企业面临着日益增长的业务需求与复杂多变的市场竞争环境。如何构建高效、稳定且具性价比的 IT 架构&#xff0c;成为众多企业突破发展瓶颈的关键所在。而亿林网络推出的 24 核 32G 裸金属服务器&#xff0c;搭配联通专线的千兆共享带…...

Web3时代的数据保护挑战与应对策略

随着互联网技术的飞速发展&#xff0c;我们正步入Web3时代&#xff0c;这是一个以去中心化、用户主权和数据隐私为核心的新时代。然而&#xff0c;Web3时代也带来了前所未有的数据保护挑战。本文将探讨这些挑战&#xff0c;并提出相应的应对策略。 数据隐私挑战 在Web3时代&a…...

Qwen3与MCP协议:重塑大气科学的智能研究范式

在气象研究领域&#xff0c;从海量数据的解析到复杂气候模型的构建&#xff0c;科研人员长期面临效率低、门槛高、易出错的挑战。而阿里云推出的Qwen3大模型与MCP协议的结合&#xff0c;正通过混合推理模式与标准化协同机制&#xff0c;为大气科学注入全新活力。本文将深入解析…...

CppCon 2015 学习:Benchmarking C++ Code

关于性能问题与调试传统 bug&#xff08;如段错误&#xff09;之间差异的分析。以下是对这一页内容的详细解释&#xff1a; 主题&#xff1a;传统问题&#xff08;如段错误&#xff09;调试流程清晰 问题类型&#xff1a;段错误&#xff08;Segmentation Fault&#xff09; …...

URL 结构说明+路由(接口)的认识

一、URL 结构说明 以这个为例&#xff1a;http://127.0.0.1:5000/zhouleifeng 1.组成部分: http://&#xff1a;协议 127.0.0.1&#xff1a;主机&#xff08;本地地址&#xff09; :5000&#xff1a;端口号&#xff08;Flask 默认 5000&#xff09; /zhouleifeng&#xff1a…...

省赛中药检测模型调优

目录 一、baseline性能二、baseline DETR head三、baseline RepC3K2四、baseline RepC3K2 SimSPPF五、baseline RepC3K2 SimSPPF LK-C2PSA界面1.引入库2.读入数据 总结 一、baseline性能 Epoch GPU_mem box_loss cls_loss dfl_loss Instances Size120/120 …...