深入浅出理解DeepSeek 3FS (3) 步步引导轻松理解内存管理,面试必看

老伟姑娘_7110

老伟姑娘_7110

2025-04-24

1071人浏览

原创

c++oding="utf-8" ?>

? 大家好,我是小王同学,

今天带你们一起探秘内存分配的奥秘! 拉倒最有也彩蛋

本文主要描述了如何为一个类自定义new实现

大胆猜测,小心求证过程中C++的new实现其实是分层设计的:➤ 第一层:调用C++标准库中的operator new➤ 第二层:operator new内部调用C语言标准库中的malloc➤ 第三层:最终由操作系统提供接口完成实际内存分配模块化替换? 在实际开发中,灵活替换默认内存分配方案非常关键:➤ 链接阶段:C++标准库中的operator new被声明为弱符号,用户只要自定义同名的强符号,就能在链接时自动替换!➤ 运行阶段:通过设置LD_PRELOAD环境变量,可以让自定义的malloc函数以更高的优先级替换原有实现。这种模块化替换机制不仅让开发者能随时调控内存分配策略,更带来无限可能!✨3FS 怎么实现的 (彩蛋 )? 3FS的实现方式相当直观:➤ 通过定义OVERRIDE_CXX_NEW_DELETE编译开关,默认情况下采用operator new调用malloc;➤ 如果开启开关,自定义分配器,可能自定义实现的,可能第三方库的Jemalloc,TCMalloc ➤ 项目中定义了一个统一的全局变量gAllocator,用于管理所有内存分配接口;➤ 对于第三方分配器,通过dlsym动态加载函数地址(如getMemoryAllocatorFunc = (GetMemoryAllocatorFunc)::dlsym(mallocLib, GET_MEMORY_ALLOCATOR_FUNC_NAME)), 实现了灵活且扩展性极强的内存管理方案。这种设计既清晰又充满智慧,让内存管理不再神秘,而是充满了DIY的乐趣! 理解如何深刻 ,这也是为选择看3fs原因,其他项目可不会这么清晰

Happy Coding~?

深入浅出理解DeepSeek 3FS (3) 步步引导轻松理解内存管理,面试必看

一 越老生常谈,越难超出期望,换个问法就不会了故事的背景

工作8年的小王 同学 为了准备c++面试, 很早 就从脉脉上搜索过类似题目,信心满满而去, 什么返回值区别,重载区别?还有函数用法区别?

深入浅出理解DeepSeek 3FS (3) 步步引导轻松理解内存管理,面试必看经典题目

经典题目

结果吊打一顿回来,在中午吃饭的时候,被老王看到了。

哈哈哈,又拒绝一家公司是对吧? 来说说这次发生了什么,有什么意想不到事情。

当时对话是这样的

面试官:请说说 new 和malloc区别?

小王脑中2小人开始不假思索推理 开来?

操作系统内存管理这个我不熟悉呀?网上看过很多文章和书籍讲虚拟内存看到迷糊,怎么回答。new 有什么可回答的, 分配内存,调用构造函数,10年前我知道了

于是我回答了

new 是运算符,运算符就支持重载,因为operator new 。。。。(这个回答是错误的)

深入浅出理解DeepSeek 3FS (3) 步步引导轻松理解内存管理,面试必看运算符

运算符

老王:

从你回答慢了 2-3秒 速度来看 , 你思路产生混乱,被题目本身限制住了 更没有和日常开发工作有效结合起来。

操作系统层面事情,内存管理事情,你不会,别人不会,不需要在这个方面思考new 实现过程 ,但是还是停留在10年前,这期间没看到你进步你可能了解,别人也了解事情 你需要探索背后原理二、大胆猜测,小心求证1. 问题能描述清楚吗?new和malloc 区别一个类如何定制自己的new和delete,代替默认的 new 和delete?(这个才是真正马甲)

小提示

new 是怎么调用operator new,是全局的operator new函数,还是每个类的operator new函数 ?2. 为了解决这个问题准备哪些事情(技术路线)step1 c++学习路线和资料(想要电子书关注 回复 电子书)【Effective C++】条款49~52:定制new和deleteMore Effective C++ Item 8: Understand the different meanings of new and deletestep2 参考源码

(1)C libstdc++**: https://gcc.gnu.org/libstdc++/

LLVM libc++: https://libcxx.llvm.org/

在标准库实现中,你可以找到 std::allocator、std::unique_ptr 和 std::shared_ptr 的实现方式。

step3 开源项目

一些高性能的 C++ 项目也有自定义 new/delete 的实现,你可以参考它们的代码:

Google TCMalloc(高性能内存分配器)oceanbase3FS

开始 干活

演示代码:

代码语言:javascript代码运行次数:0运行复制

https://github.com/watchpoints/master-cpp/blob/main/cpp/03_new.cpp#include <iostream>class Foo{public:     int number;     Foo(int n)     {       number = n;     }};int main() {    Foo* ptr = new Foo(10);    return0;}</iostream>

小王疑问1:new语法 :是c++语法的高级抽象,既然抽象根本不知道内部怎么实现的?

老王说:借助https://godbolt.org/z/YWcecfG7b 这个工具

深入浅出理解DeepSeek 3FS (3) 步步引导轻松理解内存管理,面试必看汇编实现

汇编实现

看不懂汇编没关系,直接让大模型来解释

以下是添加了详细注释的汇编代码,解释每一条指令的作用:

代码语言:javascript代码运行次数:0运行复制

__cxx_global_var_init:        push    rbp                  # 1. 保存当前栈帧指针        mov     rbp, rsp              # 2. 设置新的栈帧        sub     rsp, 32               # 3. 在栈上分配 32 字节的局部变量空间        mov     edi, 4                # 4. 设置 operator new 的参数,分配 4 字节的空间        call    operator new(unsigned long)@PLT  # 5. 调用 operator new 进行内存分配        mov     rdi, rax              # 6. 将分配的地址存入 rdi(作为 Foo::Foo(int) 构造函数的 this 指针)        mov     rax, rdi              # 7. 复制 rdi 到 rax        mov     qword ptr [rbp - 24], rax  # 8. 将 this 指针存入栈中的局部变量(用于后续操作)        mov     esi, 10               # 9. 设置 Foo::Foo(int) 构造函数的参数,传递值 10        call    Foo::Foo(int) [base object constructor]  # 10. 调用 Foo 类的构造函数        jmp     .LBB0_1               # 11. 跳转到 .LBB0_1 继续执行后续逻辑

call 指令 就是调用函数

new 实现调用2个函数

内存分配 调用全局的 operator new 函数为对象分配足够大小的内存。(4字节)调用 Foo构造对象返回对象指针 构造完毕后返回分配并初始化后的对象指针。

This operator is built into the language and, like sizeof, you can’t change its meaning: it always does the same thing.

What it does is twofold.

First, it allocates enough memory to hold an object of the type requested.Second, it calls a constructor to initialize an object in the memory that was allocated.

The new operator always does those two things; you can’t change its behavior in any way

深入浅出理解DeepSeek 3FS (3) 步步引导轻松理解内存管理,面试必看More Effective C++
深入浅出理解DeepSeek 3FS (3) 步步引导轻松理解内存管理,面试必看More Effective C++

More Effective C++

深入浅出理解DeepSeek 3FS (3) 步步引导轻松理解内存管理,面试必看More Effective C++

More Effective C++

小王疑问2:new 是c++语法 operator new是一个函数?new 内部实现自动operator new函数?他们直接关系是什么

老王:根据上面分析结果 不妨大胆猜测一下。

代码语言:javascript代码运行次数:0运行复制

+----------------------+| new 关键字 (C++)    |  [libstdc++.so]| 调用 operator new   |+----------------------+        |        v+----------------------+| std::operator new   |  [libstdc++.so]| (分配内存, 调用 malloc) |+----------------------+        |        v+----------------------+| malloc() (glibc)    |  [libc.so]| 分配内存  根据大小判断     |+----------------------+        |  +-----+------+  |            |  v            v+----------------------+   +----------------------+| brk() (堆扩展)       |   | mmap() (大块内存)   || size ≤ 128 KB        |   | size > 128 KB        || [syscall to kernel]  |   | [syscall to kernel]  |+----------------------+   +----------------------+        |                           |        v                           v+----------------------+   +----------------------+| Linux 内核管理      |   | Linux 内核管理      || 分配物理内存         |   | 分配独立映射区域    |+----------------------+   +----------------------+

各函数对应动态库

函数

作用

所在动态库

new

申请对象并调用构造函数

libstdc++.so

std::operator new

内存分配函数(调用 malloc)

libstdc++.so

malloc

申请动态内存

libc.so

brk

调整堆(小内存)

libc.so (syscall)

mmap

申请大块内存(独立映射)

libc.so (syscall)

free

释放内存

libc.so

小王疑问3:我还是不太相信new语法,用到libstdc++,这个不是一体的吗?

请 回到 演示代码部分,编译代码 ldd 查看,new 确实调用 libstdc++.so.6

ldd a.out linux-vdso.so.1 (0x00007fff6f9c0000) libstdc++.so.6 => /lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007f5117c52000) libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f5117a29000) libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007f5117942000) /lib64/ld-linux-x86-64.so.2 (0x00007f5117e8c000) libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007f511792200

老王:格局没打开 看看这个

深入浅出理解DeepSeek 3FS (3) 步步引导轻松理解内存管理,面试必看c++11
深入浅出理解DeepSeek 3FS (3) 步步引导轻松理解内存管理,面试必看c++11
深入浅出理解DeepSeek 3FS (3) 步步引导轻松理解内存管理,面试必看c++11

小王疑问4:g++ 命令 编译时候为什么默认自动连接libstdc++库吗?

简单来说,GCC是编译器,它负责将C++源代码编译成可执行程序。 在这个过程中,GCC会链接到Libc和Libstdc++这两个库。 Libc为GCC提供了底层的C语言接口, 而Libstdc++则为GCC提供了C++标准库的实现

libc是Linux下原来的标准C库,也就是当初写hello world时包含的头文件#include 定义的地方。

后来逐渐被glibc取代

那glibc都做了些什么呢? glibc是Linux系统中最底层的API,几乎其它任何的运行库都要依赖glibc。 glibc最主要的功能就是对系统调用的封装,你想想看,你怎么能在C代码中直接用fopen函数就能打开文件? 打开文件最终还是要触发系统中的sys_open系统调用,而这中间的处理过程都是glibc来完成的。

glibc是运行时库(Runtime Library)对于系统级别的事件,libstdc++首先是会与glibc交互,才能和内核通信。相比glibc来说,libstdc++就显得没那么基础了。

特性

glibc (libc)

libc++

libstdc++

用途

从 Git 仓库历史中彻底删除文件
从 Git 仓库历史中彻底删除文件

|Git删除了文件,但还会在Git 仓库历史中遗留。 - 想要从 Git 历史移除敏感信息(密码、密钥、token、私钥) - 想要删除意外提交的大文件 - 想要重写 Git 历史、移除特定文件的所有痕迹 - 提到 "force push"、"重写 commit 历史" - 需要从所有分支、所有 tag 中删除某个文件

下载

提供 C 语言标准库实现

提供 C++ 标准库实现

提供 C++ 标准库实现

支持平台

主要用于 Linux 操作系统

主要与 Clang 编译器配合使用

主要与 GCC 编译器配合使用

内容

包括所有 C 标准库函数,操作系统接口、线程支持等

提供 C++ 标准库的模板库(STL)、IO流等

提供 C++ 标准库的模板库(STL)、IO流等

库功能

系统调用、内存管理、线程库、文件操作等

C++ STL,算法,IO流等

C++ STL,算法,IO流等

重入性与线程安全

提供线程安全的库支持,包含锁等线程控制功能

提供线程安全支持(但不一定是内建的)

提供线程安全支持,包含多线程功能

自然会: 在 C++ 开发中,大多数程序都会使用标准库。**g++ 自动链接 libstdc++**,(你不说我就不知道的事情) 可以减少用户的额外操作,提高编译的便捷性,同时避免因忘记手动链接库而导致的错误。因此,这是一种用户友好的设计选择

动手实验:使用gcc编译cpp代码,gcc默认不使用c++标准库libstdc++代码语言:javascript代码运行次数:0运行复制

 如果使用 `gcc` 编译 C++ 代码(不推荐),就需要手动添加 `-lstdc++` 选项来链接标准库gcc 03_new.cpp  /usr/bin/ld: /tmp/ccZKC4VY.o: in function `main':03_new.cpp:(.text+0x13): undefined reference to `operator new(unsigned long)'gcc 03_new.cpp  -lstdc++(需要手工制定)

动手实验:使用g++命令编译cpp代码,默认链接c++标准库

g++ -v test.cpp -o test COLLECT_GCC=g++ ... /usr/lib/gcc/x86_64-linux-gnu/9/collect2 ... ... -lstdc++ ...

小王疑问5: 我相信了 new 与 std::operator new 有关系, 但是他真实一个函数吗?

operator是C++的关键字,它和运算符一起使用,表示一个运算符函数。

再次表示 new 运算符是不可以重载的,网上说错误的。

深入浅出理解DeepSeek 3FS (3) 步步引导轻松理解内存管理,面试必看image.png

image.png

代码:

https://github.com/gcc-mirror/gcc/blob/master/libstdc++-v3/libsupc++/new_op.cc

代码语言:javascript代码运行次数:0运行复制

// A freestanding C runtime may not provide "malloc" -- but there is no// other reasonable way to implement "operator new".// 一些独立的 C 运行时环境可能不提供 "malloc",但没有其他合理的方法来实现 "operator new"。// 因此,这里直接声明 `malloc` 函数,保证后续调用不会出错。extern"C"void *malloc (std::size_t);#endif// `operator new` 的弱定义版本,可以被用户自定义版本覆盖。// 当 new 关键字被调用时,会执行此函数以分配内存。_GLIBCXX_WEAK_DEFINITION void *operator new (std::size_t sz) _GLIBCXX_THROW (std::bad_alloc){void *p;// `malloc(0)` 在不同的 C 标准库实现中可能会有不确定的行为(返回 nullptr 或者有效指针)。// 为了避免这种不确定性,如果 `sz == 0`,则将其设为 1,确保 `malloc` 至少分配 1 字节。if (__builtin_expect (sz == 0, false))    sz = 1;// 循环尝试调用 `malloc`,直到成功分配内存或者 `new_handler` 处理失败while ((p = malloc (sz)) == 0) // 如果 `malloc(sz)` 返回 `nullptr`,表示内存分配失败    {      // 获取当前的 new_handler(可以通过 `std::set_new_handler` 设定)      new_handler handler = std::get_new_handler ();            // 如果没有设置 `new_handler`,则直接抛出 `std::bad_alloc` 异常,终止程序。      if (!handler)        _GLIBCXX_THROW_OR_ABORT(bad_alloc());            // 调用用户自定义的 `new_handler`,该处理程序可能会尝试释放一些内存,      // 或者执行其他策略(如记录日志、延迟执行),以便下一次 `malloc` 可能成功。      handler ();    }// 成功分配内存,返回指针return p;}https://github.com/gcc-mirror/gcc/blob/master/libstdc++-v3/libsupc++/new_op.cc// ------------------ BEGIN COPY ------------------// Implement all new and delete operators as weak definitions// in this shared library, so that they can be overridden by programs// that define non-weak copies of the functions.static void* operator_new_impl(std::size_t size) {if (size == 0)    size = 1;void* p;while ((p = std::malloc(size)) == nullptr) {    // If malloc fails and there is a new_handler,    // call it to try free up memory.    std::new_handler nh = std::get_new_handler();    if (nh)      nh();    else      break;  }return p;}

内存管理的封装:在 C 中,如果你想在堆上分配内存,你通常会使用 malloc 或 calloc,然后需要手动管理内存,包括初始化对象。在 C++ 中,new 运算符提供了一个更高级的抽象,不仅仅是内存分配,还自动调用对象的构造函数来初始化对象。这种封装隐藏了底层内存管理的细节,简化了内存分配的使用,使开发者不需要关注内存分配与对象初始化的分离。异常安全:使用 new 时,如果内存分配失败,它会抛出一个 std::bad_alloc 异常,这使得开发者不需要显式地检查 malloc 返回的 NULL 值。这样的异常处理提供了一种更加安全和优雅的内存管理方式。对内存分配的透明管理:虽然底层的 operator new 函数可以被重载,但 C++ 语言本身提供了透明的内存分配接口,允许开发者使用 new 运算符而不关心内存分配的细节。这种抽象化让开发者集中于高层次的程序逻辑,而不是低级的内存管理。

https://en.cppreference.com/w/cpp/memory https://en.cppreference.com/w/cpp/memory/new

深入浅出理解DeepSeek 3FS (3) 步步引导轻松理解内存管理,面试必看image.png

image.png

特性

malloc (函数)

new (运算符)

是否是函数?

✅ 是普通函数

不是函数,是运算符

是否调用构造函数?

不会调用

✅ 会调用构造函数

返回类型

void*,需要强制转换

直接返回正确的指针类型

是否可以重载?

❌ 不能重载

✅ operator new 可以被重载

是否支持类型安全?

❌ 需要手动转换类型

✅ 自动匹配类型

释放方式

free(ptr);

delete ptr;

失败时返回

nullptr / NULL

抛出异常(std::bad_alloc)

小王疑问6: call operator new(unsigned long)@PLT PLT 是什么? operator new 函数 weak definitions 又是什么

老王:

深入浅出理解DeepSeek 3FS (3) 步步引导轻松理解内存管理,面试必看image.png

image.png

推荐看一本书 程序员的自我修养—链接、装载与库 补一补。

深入浅出理解DeepSeek 3FS (3) 步步引导轻松理解内存管理,面试必看image.png

最喜欢一句话 Any problem in computer science can be solved by anotherlayer of indirection.”

这句话几乎概括了计算机系统软件体系结构的设计要点,整个体系结构从上到下都是按照严格的层次结构设计的

深入浅出理解DeepSeek 3FS (3) 步步引导轻松理解内存管理,面试必看image.png

@PLT 含义是什么

PLT 是 ELF 文件中的一块代码区域,用于动态链接时的函数调用延迟绑定。

也就是说,当程序第一次调用一个外部函数(如来自共享库的函数)时, 实际调用的是 PLT 中的一个“跳板”入口,经过解析后再跳转到真正的函数地址;

而后续调用则直接通过更新过的 GOT 表项(全局偏移表)跳转到目标函数。

动手验证:例子越简单越好

假设有下面的 C 程序

代码语言:javascript代码运行次数:0运行复制

#include <stdio.h>int main() {    printf("Hello, PLT!\n"); //使用libc这个动态库    return 0;}</stdio.h>

当你编译这个程序(使用动态链接库,例如 libc)后,编译器不会直接把 printf 的真实地址写进代码中,而是生成一条调用“printf@PLT”的指令。

符号(Symbol)这个概念随着汇编语言的普及迅速被使用,它用来表示一个地址,这个地址可能是一段子程序(后来发展成函数)的起始地址,也可以是一个变量的起始地址。

也就是说,程序在运行时会先跳转到 PLT 表中的一个“跳板”入口,而不是直接跳转到 libc 中的 printf 函数。

#动态链接中PLT与GOT工作流程

第一次调用时(延迟绑定): 当程序第一次调用 printf 时,实际执行的是 printf@PLT 中的代码。该代码的第一条指令通常是跳转到 printf 对应的 GOT 表项(Global Offset Table,全球偏移表)。由于这是第一次调用,这个 GOT 表项里并没有存储真实的 printf 地址,而是保存了一个指向特殊解析代码的地址(通常会指向 PLT 的第一项,也称为共公 PLT 表项)。解析函数地址: 随后,这个特殊的解析代码会调用动态链接器(如 _dl_runtime_resolve),根据传入的信息(例如函数的编号、模块信息等),找到真正的 printf 地址,并将这个地址写入到 GOT 表中对应的 printf 表项。后续调用直接跳转: 此后,再次调用 printf 时,程序跳转到 printf@PLT,这时通过 GOT 表查到的地址已经是解析后的 printf 真实地址,直接跳转过去执行,从而省去了再次解析的步骤

首先, 我们要知道, GOT和PLT只是一种重定向的实现方式. 所以为了理解他们的作用, 就要先知道什么是重定向, 以及我们为什么需要重定向

比如某个公司开发完成了某个产品,它按照一定的规则制定好程序的接口,其他公司或开发者可以按照这种接口来编写符合要求的动态链接文件。

该产品程序可以动态地载入各种由第三方开发的模块,在程序运行时动态地链接,实现程序功能的扩展

弱符号的用途

弱符号主要用于:

库函数的默认实现operator new 就是一个典型案例,标准库提供 默认的弱定义,用户可以 提供自己的强定义 进行替换。可选的全局变量在某些场景下,一个库可能会提供一个 默认变量,但允许应用程序提供自己的变量:三、这才是刚刚开始,3FS还到呢,怎么为类定制的new操作

动手验证:

代码语言:javascript代码运行次数:0运行复制

new

相关文章

AI工具
AI工具

AI 智能聊天, 问答助手, AI 智能搜索, 免费无限量使用 DeepSeek R1 模型,支持联网搜索。

下载

本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

相关专题

更多
什么是分布式
什么是分布式

分布式是一种计算和数据处理的方式,将计算任务或数据分散到多个计算机或节点中进行处理。本专题为大家提供分布式相关的文章、下载、课程内容,供大家免费下载体验。

2023.08.11

2113

5

分布式和微服务的区别
分布式和微服务的区别

分布式和微服务的区别在定义和概念、设计思想、粒度和复杂性、服务边界和自治性、技术栈和部署方式等。本专题为大家提供分布式和微服务相关的文章、下载、课程内容,供大家免费下载体验。

2023.10.07

3414

5

c语言中null和NULL的区别
c语言中null和NULL的区别

c语言中null和NULL的区别是:null是C语言中的一个宏定义,通常用来表示一个空指针,可以用于初始化指针变量,或者在条件语句中判断指针是否为空;NULL是C语言中的一个预定义常量,通常用来表示一个空值,用于表示一个空的指针、空的指针数组或者空的结构体指针。

2023.09.22

549

3

java中null的用法
java中null的用法

在Java中,null表示一个引用类型的变量不指向任何对象。可以将null赋值给任何引用类型的变量,包括类、接口、数组、字符串等。想了解更多null的相关内容,可以阅读本专题下面的文章。

2024.03.01

1698

6

java基础知识汇总
java基础知识汇总

java基础知识有Java的历史和特点、Java的开发环境、Java的基本数据类型、变量和常量、运算符和表达式、控制语句、数组和字符串等等知识点。想要知道更多关于java基础知识的朋友,请阅读本专题下面的的有关文章,欢迎大家来php中文网学习。

2023.10.24

6224

49

Go语言中的运算符有哪些
Go语言中的运算符有哪些

Go语言中的运算符有:1、加法运算符;2、减法运算符;3、乘法运算符;4、除法运算符;5、取余运算符;6、比较运算符;7、位运算符;8、按位与运算符;9、按位或运算符;10、按位异或运算符等等。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

2024.02.23

3224

5

php三元运算符用法
php三元运算符用法

本专题整合了php三元运算符相关教程,阅读专题下面的文章了解更多详细内容。

2025.10.17

1852

13

printf用法大全
printf用法大全

php中文网为大家提供printf用法大全,以及其他printf函数的相关文章、相关下载资源以及各种相关课程,供大家免费下载体验。

2023.06.20

868

5

fprintf和printf的区别
fprintf和printf的区别

fprintf和printf的区别在于输出的目标不同,printf输出到标准输出流,而fprintf输出到指定的文件流。根据需要选择合适的函数来进行输出操作。更多关于fprintf和printf的相关文章详情请看本专题下面的文章。php中文网欢迎大家前来学习。

2023.11.28

1029

10

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Python uv官方安装与入门文档
Python uv官方安装与入门文档

共0课时 | 0人学习

Buffalo框架快速入门指南
Buffalo框架快速入门指南

共0课时 | 0人学习

Valgrind支持平台说明
Valgrind支持平台说明

共0课时 | 0人学习