面试周刊(4):类自定义 new/delete 实现?三阶段(编译/链接/运行)一口气讲透

本文涉及的产品
RDS PostgreSQL Serverless,0.5-4RCU 50GB 3个月
推荐场景:
对影评进行热评分析
阿里云百炼推荐规格 ADB PostgreSQL,4核16GB 100GB 1个月
云原生数据库 PolarDB PostgreSQL 版,标准版 2核4GB 50GB
简介: 面试周刊

各位老师好!


这是CPP面试冲刺周刊 (c++ weekly)
陪你一起快速冲击大厂面试 第四期


周刊目标



  • 不是成为C++专家,而是成为C++面试专家

本期内容



  • 如何为一个类自定义new/delete

一页PPT 解释: (回答有逻辑)





维度
① 类内 new/delete
② 全局 new/delete(弱符号)
③ LD_PRELOAD + tcmalloc




生效时机
编译期(静态绑定)
链接期(符号强弱覆盖)
运行期(动态装载器优先级)


作用范围
仅该类/基类查找链
全局(除已绑定到类内的 new)
全局(动态库与主程序)


机制
作用域查找 + 非虚(无 vtable)
弱符号 vs 强符号
预加载库先解析同名符号


是否依赖弱符号

是(libstdc++ 默认全局 new 为 weak)
否(依赖 ld.so 解析顺序)


源码是否需改动
需要(在类内写函数)
需要(提供全局 operator new/delete)
不需要(设置环境变量即可)


常见用途
类级内存池、对齐、对象复用
全局策略(统计/替换分配器/统一注入)
线上快速切换分配器/排查内存/零侵入部署



题目来源C++ Primer 第 18 章节



  • Exercise 18.9: Declare members new and delete for the QueueItem class.
  • 18.1.6 Class Specific new and delete
  • Exercise 18.6: Reimplement your Vector class to use operator new, operator delete, placement new, and direct calls to the destructor.

课本上的题目绝对经典课本上的题目绝对经典

整体知识看板(看文末):



  • 第一周:c++基础知识高频面试题解析【当前位置】
  • 第二周: 专注分布式存储,数据库广告搜索 Ai 辅助驾驶 大厂热门后端开发岗位拆解。
  • 第三周:系统架构设计,用未来 10 年发展目标,重新设计原来系统

开始


一、面试官:如何为一个类自定义new/delete


C++ 对象的内存分配默认依赖 全局 operator new / operator delete
本质上调用 libc malloc/free,最终走 系统调用 brk/mmap


答案可能很简单(这绝对不是最终结果,面试官要反问的)


#include <iostream>
using namespace std;

struct A {
    void operator new(size_t sz) {
        cout << "A::operator new, size = " << sz << endl;
        return ::operator new(sz);
    }
    void operator delete(void
 p) {
        cout << "A::operator delete" << endl;
        ::operator delete(p);
    }
};
int main() {
    A a = new A;  // 输出: A::operator new, size = 1
    delete a;      // 输出: A::operator delete
}


如何和已有知识,一步步结合起来,


下面是我推导过程,可能意想不到发现


二、小青回答(工作0-3 年 青铜)


如何如何为一个类自定义new/delete
完全之前从来没有遇到过问题,
很简单,平时不总结,别指望 面试当成超常发挥,


我猜 你可能这样回顾之前准备



  • new 和 malloc 有什么区别
  • new 申请失败返回 NULL 还是抛出异常
  • 自定义一个类函数,虚函数可以吗,好像不行
  • operator new 还是选择 placement new

暂停 思绪无限发散


重新整理 历史题目的的关系


1.1 普通的函数:



函数特性 重载(overload),隐藏(hide),覆盖(override)




  • 重载(overload):在相同访问内(一个类),函数名相同,参数不同(c 语言不支持这样重载


  • 隐藏(hide): 不同范围内 ,派生类定义与基类同名非虚函数时,基类同名函数被隐藏


  • 覆盖 / 重写(Override):基类虚函数被派生类虚函数覆盖,运行期判断


    C++多态必要条件
    ① 继承(this 指针)
    ② 虚函数重写 ,
    ③ 父类指针/引用指向子类对象




画外音:目前就是动态绑定无法解决这个问题,




  • operator new 是默认 static 函数,static 函数无法访问 this 指针,不支持运行时多态

1.2 普通函数,库函数,系统调用


参考:序员的自我修养:链接、装载与库



  • new 是c++运算符,不能重载,语法规定的
  • operator new 是c++ libstdc++ 标准库函数,静态函数,静态函数自然是不能是虚函数

_GLIBCXX_WEAK_DEFINITION void 
operator new (std::sizet sz) GLIBCXX_THROW (std::bad_alloc)



  • malloc 是 c 语言 glibc 标准库的库函数
  • brk是系统调用

演示:


new 实现调用2个函数



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

alt

 Foo ptr = new Foo();
   │
   ▼
查找类内 operator new
   │
   ├─存在 → 调用类内 new
   └─不存在 → 调用全局 ::operator new
   │
   ▼
内存分配(malloc / 内存池 / 用户态栈)
   │
   ▼
调用构造函数
   │
   ▼
返回对象指针




alt

1.3 c++角度无法解决这个问题了,c 语言不支持重载,如何解决重载问题 弱符号,编译期静态绑定





概念
实现方式
发生时机
影响范围




函数重载 (overloading)
同一作用域内多个函数签名不同
编译期
仅 C++ 语法


虚函数覆盖 (overriding)
子类重写父类虚函数
运行时
仅多态场景


符号覆盖 (symbol overriding)
通过弱符号 + 链接器选择强符号
链接期
整个可执行文件/动态库



如何验证 libstdc++ 的 operator new 是弱符号

我们可以直接用 nm 命令验证:


nm -C /usr/lib/x86_64-linux-gnu/libstdc++.so | grep "operator new"


典型输出类似:


0000000000098e80 W operator new(unsigned long) 0000000000098ed0 W operator new 0000000000098f20 W operator delete(void)


这里的 W 就表示 weak symbol


为什么 libstdc++ 把 operator new/delete 定义为弱符号


来看 libstdc++ 的源码(以 GCC 13.x 为例):


注意到 _GLIBCXX_WEAK_DEFINITION,在 GCC 平台上一般展开为:


#define _GLIBCXX_WEAK_DEFINITION attribute ((weak))


这意味着 libstdc++ 提供的 operator new 是弱符号


原因:为了支持用户自定义重载


在 Linux 下,符号解析遵循 ELF 链接规则



  • 弱符号(weak):如果存在同名的强符号(strong),那么最终可执行文件会绑定到强符号。


  • 强符号(strong):用户自己实现的 operator new 会被视为强符号。



因此:



  • 如果用户自己实现了全局 operator new会自动覆盖 libstdc++ 提供的版本。
  • 如果用户没有实现,则默认使用 libstdc++ 的弱符号实现。

这就是为什么 libstdc++ 必须把它们标记为弱符号,否则用户无法重载



到这里你是否感觉找到最终答案 NO,c++继承复杂之处在这里,
上面operator new是全局符号,如果有继承的类呢?



三 、小白回答(工作 3-5 年 白银)


如果面试官问:



为什么类内 operator new/delete 是静态绑定,而全局 operator new/delete 是弱符号?



可以回答:



  1. 全局版本是由 libstdc++ 提供的默认实现,在源码中标记了 attribute((weak)),用户可以通过定义自己的全局版本来覆盖默认实现 → 弱符号机制


  2. 类内版本是一个 普通静态成员函数,绑定过程在 编译期完成,属于 静态绑定,不会走虚函数表,也不会受弱符号影响。


  3. 当类内和全局版本同时存在,类内优先



30 秒总结自定义类实现 new 和/delete:



  • 第一是类内 operator new/delete,纯编译期静态绑定,作用域查找,不涉及弱符号;


  • 第二层是全局 operator new/delete,libstdc++ 默认实现是弱符号,你提供强符号即可链接期覆盖;


  • 第三层是LD_PRELOAD + tcmalloc,通过动态装载器在运行时优先解析同名符号,实现零改码劫持。



三者分别对应编译期、链接期、运行期三个阶段,


优先级依次是:类内静态绑定 > 运行期预加载 > 链接期弱符号


2.1 深入理解 C++ new/delete:类内静态绑定 vs 全局弱符号机制





维度
类内 operator new/delete
全局 ::operator new/delete




作用域
限于该类及其派生类
全局可见


绑定时机
编译期静态绑定
链接期符号解析


多态性
❌ 无多态
❌ 无多态


弱符号
❌ 不依赖弱符号
✅ 是弱符号


优先级
优先于全局 ::operator new
最后兜底



#include <iostream>
#include <new>
struct Base
{

 static void operator new(std::size_t size)
 
{
  std::cout << "Base new\n";
  return ::operator new(size);
 }
};

struct Derived : Base {

};

int main()
{
 //A class-specific operator new is looked up in the scope of the class and is not virtual.
 
 Derived
 p = new Derived; // 调用 Base::operator new 还是 Derived::operator new?
}


  • 编译期静态绑定

    new Derived 的查找顺序是:

    1. Derived 中查找 operator new
    2. 如果没找到,查找 Base
    3. 如果都没找到,最后使用全局 ::operator new

  • Derived 没有定义 operator new,所以直接用到了 Base::operator new
  • 这不是多态,不需要虚函数表。

最终结论



  • 类内 operator new/delete

    • 是静态成员函数
    • 编译期静态绑定
    • 和弱符号无关

  • 全局 operator new/delete

    • libstdc++ 提供默认实现
    • 被标记为 weak symbol
    • 用户可覆盖


2.2 编译期,链接期,运行期


alt



维度
① 类内 new/delete
② 全局 new/delete(弱符号)
③ LD_PRELOAD + tcmalloc




生效时机
编译期(静态绑定)
链接期(符号强弱覆盖)
运行期(动态装载器优先级)


作用范围
仅该类/基类查找链
全局(除已绑定到类内的 new)
全局(动态库与主程序)


机制
作用域查找 + 非虚(无 vtable)
弱符号 vs 强符号
预加载库先解析同名符号


是否依赖弱符号

是(libstdc++ 默认全局 new 为 weak)
否(依赖 ld.so 解析顺序)


源码是否需改动
需要(在类内写函数)
需要(提供全局 operator new/delete)
不需要(设置环境变量即可)


常见用途
类级内存池、对齐、对象复用
全局策略(统计/替换分配器/统一注入)
线上快速切换分配器/排查内存/零侵入部署



① 类内 operator new/delete(静态绑定)


源码:
struct Base {
  static void operator new(std::size_t);
};
struct Derived : Base {};

new Derived
   │
   ├─ 编译器做“名字查找”(先 Derived,后 Base)
   │
   ├─ 若 Derived 未定义 → 绑定到 Base::operator new   ←←← 静态绑定(非多态)
   │
   └─ 生成直接调用指令(无 vtable,无符号覆盖参与)



要点



  • 编译期决定,不走弱符号、不走 vtable。
  • 仅影响该类(及查找到的基类作用域),优先级高于全局

② 全局 operator new/delete(弱符号覆盖,链接期)


源文件们  ──(编译)──► 目标文件们(含符号表) ──(链接)──► 可执行文件 / so

libstdc++ 提供:
  _GLIBCXX_WEAK_DEFINITION
  void
 ::operator new(std::size_t);     ← 弱符号(weak)

你的工程若提供:
  void ::operator new(std::size_t);     ← 强符号(strong)

链接器规则:

  若同名强符号存在 → 选择强符号(你的全局 new
  
  否则 → 选择弱符号(libstdc++ 默认 new



LD_PRELOAD + tcmalloc(动态链接优先级,运行期)


运行命令:
  LD_PRELOAD=/usr/lib/libtcmalloc.so ./app

动态装载器 ld.so 加载顺序:
  1) 先装载 LD_PRELOAD 指定的 so(优先级最高)
  2) 再装载主程序与其依赖的其它 so(libc, libstdc++, ...)

符号解析:
  当需要解析 "malloc/free/new/delete" 时
  ├─ 如果在预加载的 tcmalloc.so 中已定义 → 直接绑定到 tcmalloc 版本
  └─ 否则继续在后续库中查找(如 libc)

效果:
  不改源码,即可把 malloc/free/new/delete 劫持到 tcmalloc


要点



  • 运行时生效,由动态装载器决定符号优先级。
  • 对主程序和动态库均可生效(除非静态链接/受限环境)。
  • 常用于快速切换分配器/排查内存问题

四、小王回答( 工作 5-10 年 ):用起来



工作 10 年和工作 3 年 在知识不会任何新增 ,结合 3fs 代码说明



4.1 DeepSeek 3FS 灵活内存池实现策略



  • 自定义:用默认系统的
  • 自定义:用第三方库的
  • 自定义:自己实现,这个不是分布式存储重点,没有实现。

代码分析:


1. 定义宏开关 CMakeLists.txt


option(OVERRIDE_CXX_NEW_DELETE "Override C++ new/delete operator" OFF)


2. 重载全局函数 operator new/delete



  • src\memory\common\OverrideCppNewDelete.h
  • 代码


#ifdef OVERRIDE_CXX_NEW_DELETE

// Override global new/delete with custom memory allocator.
void 
operator new(size_t size) { return hf3fs::memory::allocate(size); }

void operator delete(void mem) noexcept { hf3fs::memory::deallocate(mem); }

#endif


3. dlopen方式加载 动态库



  • src/memory/common/GlobalMemoryAllocator.cc


static void loadMemoryAllocatorLib()
 void 
mallocLib = nullptr;
 GetMemoryAllocatorFunc getMemoryAllocatorFunc = nullptr;
 mallocLib = ::dlopen(mallocLibPath, RTLD_NOW | RTLD_GLOBAL);
 
 gAllocator = getMemoryAllocatorFunc();

//
//这里通过环境变量 MEMORY_ALLOCATOR_LIB_PATH 来指定要加载的内存分配器库。 
//例如,如果你想使用jemalloc.tcmalloc: 
//set MEMORY_ALLOCATOR_LIB_PATH=D:\path\to\jemalloc.dll 
//set MEMORY_ALLOCATOR_LIB_PATH=D:\path\to\tcmalloc.dll
void allocate(size_t size)

 if (gAllocator == nullptr)
 
  mem 
std::malloc(allocateSize);
 
 else
  mem = gAllocator->allocate(allocateSize)


提供 GetMemoryAllocatorFunc 函数指针类型用于通过固定导出符号加载实现(如 dlsym("getMemoryAllocator")):



class MemoryAllocatorInterface {

public:

 virtual ~MemoryAllocatorInterface() = default;
 
 virtual void 
allocate(size_t size) = 0;
 
 virtual void deallocate(void mem) 0;
 
 virtual void 
memalign(size_t alignment, size_t size) = 0;
 
 virtual void logstatus(char buf, size_t size) 0;
 
 virtual bool profiling(bool active, const char 
prefix) = 0;

};
 using GetMemoryAllocatorFunc = MemoryAllocatorInterface ()();

// namespace hf3fs::memory



4.2 疑问:智能指针在自定义分配器情况不能调用默认的 delete 操作


要点



  • 谁分配,谁释放;分配与释放函数必须匹配。
  • 对象池/自定义分配接口返回的内存,务必使用自定义 deleter;默认 delete 不适用。
  • 启用 3FS 全局 new/delete 重载时,默认智能指针 deleter 可直接用

  void mem = hf3fs::memory::memalign(alignof(My), sizeof(My));
  My
 obj = new (mem) My(...);
  
  auto deleter = {
    p->~My(); //可以直接调用析构函数
    hf3fs::memory::deallocate(p); //归还到内存池
  };
  
  std::unique_ptr<My, decltype(deleter)up(obj, deleter);


五、广告时间


c++周刊目的陪你一起快速冲击大厂面试



小提示:不要把他看成一个出售给你产品,我只出售给自己
在公司做任何事情事情,
都必须清楚拆解需求功能,开发周期,最后得到什么结果,
同样面试准备也是如此,给自己一个期限 21 天,给自己大纲,然后给自己 21 天学习结果,这样自己才能安心准备下去。



曾经有一个让我心跳加速的岗位放在我面前,

我没有珍惜。

等到别人拿到 offer 的那一刻,

我才追悔莫及!


人世间,最痛苦的事情,

不是没钱吃饭,

也不是没房没车,

而是——错过了那个能让我逆天改命的机会!


如果上天再给我一次机会,

我一定会对那个岗位说三个字:

“我要你!”


如果非要在这份“心动”上加一个期限,

一万年太久了……

我只想要——21天!


你可能面临两种选择


① 犹豫不前:准备到天荒地老


“这个岗位太难了,我先准备一下吧。”

于是你准备1天、1周、1个月、1年……

等再回头,3年就这样过去了



  • 每天忙着搬砖,没时间系统复习
  • 每次想起要准备,又感觉心里没底
  • 面试知识点更新太快,拿着旧地图找新机会
    最后,错过了一次又一次心动的岗位。

② 盲目回答:机会就在眼前,却抓不住


终于等来一场面试,

你觉得问题很简单,张口就答,

结果用“几千元思维”回答“百万年薪岗位”。



  • 面试官问到C++底层实现,答不上来
  • 设计题说到高并发架构,没实战经验
  • 一紧张,连项目里真实做过的东西都讲不清

一次面试失利,也许就意味着和理想岗位失之交臂。


更残酷的是


在你犹豫的这几年里,

找工作的成本越来越高:



  • 一个部门、一个领导,可能坚持一年就被解散
  • 一个项目,可能在10年、20年后,

    曾经复杂的业务规则、先进的架构,早已被淘汰
  • 市场上新的技术和面试要求,每年都在不断升级

等你回过头来,发现不仅机会没了,

连准备的方向都变了


21天C++面试冲刺周刊
​一万年太久,只争三周​


不是让你成为C++专家, 而是让你成为C++面试专家


不是让你疯狂学习新知识, 而是帮你重新整理已有知识

让你的能力与面试题精准对齐。


因为,21天就够了,

足够让我火力全开,



  • 一边补齐 C++ 知识点,
  • 一边刷爆经典面试题,
  • 一边撸穿开源项目,
  • 让自己变得不可替代!

核心方法论



让你学到每个 c++知识,都关联一个经典面试,并对对应开源项目实践




  • 系统备战

    每天 20~30 分钟,聚焦 C++ 核心知识,

    三周时间完成高效梳理。
  • 经典面试题

    每个知识点都关联一个高频面试题

    让你知道“为什么考”和“怎么答”。
  • 开源项目实践

    通过真实项目理解底层原理,

    不背答案,而是用实践打动面试官
  • 场景驱动学习

    还原真实面试场景,

    帮你学会“怎么说服面试官”。

21天,你会获得什么?



  • 一份完整的C++面试知识地图
  • 一套高频题+解析+项目实践组合拳
  • 一次全链路模拟面试体验
  • 三周后,面对面试官,你能自信说出:

    “问吧,准备好了。”


这也是我的面试方法:




  • 如果一开始就直接学某个知识点,我常常感觉不到它的实际价值。


  • 所以我会先尝试树立一个整体的大局观,就算过程中被现实“啪啪打脸”了又怎样?


  • 把每一次面试都当成一场陪练,用面试官的专业视角和真实项目来反推和校正自己的理解,不是更好吗?这种即时、高质量的反馈,是你看多少书、自己一个人闷头琢磨多久,都很难获得的。
    整体知识看板(欢迎提供更多线索):


  • 第一周:c++基础知识高频面试题解析【当前位置】
    alt


  • 第二周: 专注分布式存储,数据库广告搜索 Ai 辅助驾驶 大厂热门后端领域项目(基本功)拆解


  • 第三周:系统架构设计,用未来 10 年发展目标,重新设计原来系统



目录
相关文章
|
25天前
|
缓存 安全 编译器
C++面试周刊(3):面试不慌,这样回答指针与引用,青铜秒变王者
《C++面试冲刺周刊》第三期聚焦指针与引用的区别,从青铜到王者级别面试回答解析,助你21天系统备战,直击高频考点,提升实战能力,轻松应对大厂C++面试。
76 10
C++面试周刊(3):面试不慌,这样回答指针与引用,青铜秒变王者
|
24天前
|
存储 消息中间件 人工智能
Lazada 如何用实时计算 Flink + Hologres 构建实时商品选品平台
本文整理自 Lazada Group EVP 及供应链技术负责人陈立群在 Flink Forward Asia 2025 新加坡实时分析专场的分享。作为东南亚领先的电商平台,Lazada 面临在六国管理数十亿商品 SKU 的挑战。为实现毫秒级数据驱动决策,Lazada 基于阿里云实时计算 Flink 和 Hologres 打造端到端实时商品选品平台,支撑日常运营与大促期间分钟级响应。本文深入解析该平台如何通过流式处理与实时分析技术重构电商数据架构,实现从“事后分析”到“事中调控”的跃迁。
259 55
Lazada 如何用实时计算 Flink + Hologres 构建实时商品选品平台
|
24天前
|
数据采集 JSON 监控
Python高效工作必备:20个实用脚本推荐!
Python是提升效率的终极自动化利器!本文精选20个实用脚本,覆盖文件批量处理、数据清洗转换、网络爬取、邮件通知、系统监控等高频场景,每项均附完整代码,可直接复制使用。无需深厚编程基础,用几行代码就能节省数小时手动操作,让你的工作流全面自动化,轻松成为高效能人士!
|
24天前
|
人工智能 供应链 数据可视化
工作流梳理工具实战教程:手把手教你绘制第一张自动化流程图
本文剖析了团队因流程混乱导致重复劳动和效率低下的问题,提出通过工作流梳理提升协作效率的解决方案。总结了流程梳理的六大核心需求,并深度测评了6款主流工具,国内有板栗看板那,国外有kiss flow结合团队规模与需求提供选型建议,助力企业高效落地流程优化。
|
24天前
|
人工智能 运维 数据可视化
2025年中国十大低代码开发平台(最新版)
在数字经济加速发展的背景下,低代码开发平台正重塑企业软件开发模式。IDC报告显示,中国低代码市场年复合增长率达35.8%。本文从技术能力、行业覆盖、生态建设与客户案例四大维度,解析2025年度主流低代码平台,涵盖普元、阿里云宜搭、腾讯微搭、用友YonBuilder等十大平台,助力企业选型布局数字化未来。
293 7
|
24天前
|
存储 消息中间件 人工智能
Fluss:重新定义实时数据分析与 AI 时代的流式存储
Apache Fluss(孵化中)是新一代流式存储系统,旨在解决传统架构中数据重复复制、高成本与复杂性等问题。它基于 Apache Arrow 构建,支持列式存储、实时更新与高效查询,融合流处理与湖仓架构优势,适用于实时分析、AI 与多模态数据场景。Fluss 提供统一读写、冷热分层与开放生态,已在阿里巴巴大规模落地,助力企业实现低成本、高效率的实时数据处理。
214 25
|
21天前
|
人工智能 自然语言处理 机器人
AI Compass前沿速览:Jetson Thor英伟达AI计算、Gemini 2.5 Flash Image、Youtu腾讯智能体框架、Wan2.2-S2V多模态视频生成、SpatialGen 3D场景生成模型
AI Compass前沿速览:Jetson Thor英伟达AI计算、Gemini 2.5 Flash Image、Youtu腾讯智能体框架、Wan2.2-S2V多模态视频生成、SpatialGen 3D场景生成模型
AI Compass前沿速览:Jetson Thor英伟达AI计算、Gemini 2.5 Flash Image、Youtu腾讯智能体框架、Wan2.2-S2V多模态视频生成、SpatialGen 3D场景生成模型

热门文章

最新文章