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

C++学习笔记 48 跟踪内存分配

内存是非常重要的东西 知道你的程序什么时候分配内存 特别是堆内存 是很有用的 如果知道程序在哪里分配内存 就有可能减少它 从而优化程序 也可以更好地了解程序是如何工作的

需要重写new运算符 来检测发生的内存分配 我们可以通过在operator new中加入一个断点 来追踪这些内存分配的来源

#include <iostream>void* operator new(size_t size)
{std::cout << "Allocating " << size << " bytes\n";return malloc(size);
}struct Object
{int x, y, z;
};int main()
{Object* obj = new Object;std::string string = "Miku";
}

在return malloc(size);这一行(第7行)设置断点 查看调用堆栈

Project_test.exe!operator new(unsigned __int64 size) 行 7
Project_test.exe!main() 行 17

所以就是Object* obj = new Object; 这一行调用了new

std::string string = "Miku"; 这就不会发生堆分配 因为这是小字符串 但是debug模式下仍然会发生分配 查看调用堆栈

Project_test.exe!operator new(unsigned __int64 size) 行 7
Project_test.exe!std::_Default_allocate_traits::_Allocate(const unsigned __int64 _Bytes) 行 87

对调用堆栈的第2行 右键 - 转到源代码 可以看到

// 来自于<xmemory>
struct _Default_allocate_traits {__declspec(allocator) static
#ifdef __clang__ // Clang and MSVC implement P0784R7 differently; see GH-1532_CONSTEXPR20
#endif // defined(__clang__)void* _Allocate(const size_t _Bytes) {return ::operator new(_Bytes);}

是在这里调用了operator new

如果把调用堆栈的显示外部代码关掉 就会变成

Project_test.exe!operator new(unsigned __int64 size) 行 7
[外部代码]
Project_test.exe!main() 行 17
[外部代码]

如果使用智能指针std::unique_ptr obj = std::make_unique(); 而不是显式地调用new

Project_test.exe!operator new(unsigned __int64 size) 行 8
Project_test.exe!std::make_unique<Object,0>() 行 3465

对调用堆栈的第2行转到源代码

// 来自于<memory>
_EXPORT_STD template <class _Ty, class... _Types, enable_if_t<!is_array_v<_Ty>, int> = 0>
_NODISCARD_SMART_PTR_ALLOC _CONSTEXPR23 unique_ptr<_Ty> make_unique(_Types&&... _Args) { // make a unique_ptrreturn unique_ptr<_Ty>(new _Ty(_STD forward<_Types>(_Args)...));
}

make_unique是调用了new

#include <iostream>
#include <memory>void operator delete(void* memory)
{free(memory);
}struct Object
{int x, y, z;
};int main()
{{std::unique_ptr<Object> obj = std::make_unique<Object>();}
}

在free(memory);这行设置断点 查看调用堆栈

Project_test.exe!operator delete(void * memory) 行 6
Project_test.exe!operator delete(void * block, unsigned __int64 __formal) 行 32
Project_test.exe!std::default_delete::operator()(Object * _Ptr) 行 3170
Project_test.exe!std::unique_ptr<Object,std::default_delete>::~unique_ptr<Object,std::default_delete>() 行 3282
对调用堆栈的第4行查看源代码 这是unique_ptr的析构函数

// 来自于<memory>
_CONSTEXPR23 ~unique_ptr() noexcept {if (_Mypair._Myval2) {_Mypair._Get_first()(_Mypair._Myval2);}
}

对_Mypair速览定义 _Compressed_pair<_Dx, pointer> _Mypair;

对_Dx速览定义 定位到了

_EXPORT_STD template <class _Ty, class _Dx /* = default_delete<_Ty> */>
class unique_ptr {
// ...
稍微往下几行也找到了 using deleter_type = _Dx; 说明_Dx是个删除器(deleter)类型

所以_Mypair._Get_first()(_Mypair._Myval2)就是调用删除器删除了指针 我们现在就需要找到删除器的具体实现 这样才能到达下一个调用堆栈

注意到对于_Dx的注释/* = default_delete<_Ty> */ 我们猜想实现删除器的类名字应该就叫default_delete 但假如没有这个注释 大概就只能依靠直觉 或者ctrl+F搜索delete 慢慢找

struct default_delete { // default deleter for unique_ptrconstexpr default_delete() noexcept = default;template <class _Ty2, enable_if_t<is_convertible_v<_Ty2*, _Ty*>, int> = 0>_CONSTEXPR23 default_delete(const default_delete<_Ty2>&) noexcept {}_CONSTEXPR23 void operator()(_Ty* _Ptr) const noexcept /* strengthened */ { // delete a pointerstatic_assert(0 < sizeof(_Ty), "can't delete an incomplete type");delete _Ptr;}
};

注释中写到 这确实是unique_ptr的默认删除器 在operator()发生了delete

现在我们对调用堆栈的第3行查看源代码 这正是default_delete的operator()

_CONSTEXPR23 void operator()(_Ty* _Ptr) const noexcept /* strengthened */ { // delete a pointerstatic_assert(0 < sizeof(_Ty), "can't delete an incomplete type");delete _Ptr;
}

当你写delete _Ptr 编译器会根据对象类型和上下文 选择合适的operator delete重载 从C++17开始 如果编译器知道对象的大小 (比如有类型信息) 它就会优先调用带size_t参数的operator delete(void, size_t) 而不是operator delete(void) 我们在使用 std::make_unique()分配对象时 编译器已经能确定Object的大小 所以在delete时会选择带有size的重载

对调用堆栈的第2行查看源代码

// 来自于delete_scalar_size.cpp
_CRT_SECURITYCRITICAL_ATTRIBUTE
void __CRTDECL operator delete(void* const block, size_t const) noexcept
{operator delete(block);
}

这个delete_scalar_size.cpp是一个很短的文件 是C++17 新增的重载

在这个含有size的operator delete内部 实际上还是调用了不含size的operator delete 所以它最终还是会调用我们在main.cpp重载的operator delete 这就是转发

调用堆栈的第1行 正是我们在main.cpp里自己重载的delete

至此 我们终于完成了一次delete

既然C++17的operator delete支持size_t参数 那么可以在我们的main.cpp里重载delete 增加对于size的输出

operator delete(void* memory, size_t size)
{std::cout << "Deleting " << size << " bytes\n";free(memory);
}

现在再去查看调用栈 就没有调用delete_scalar_size.cpp的operator delete(void, size_t) 这是因为编译器优先调用了我们重载的这个operator delete(void, size_t)版本

struct AllocationMetrics
{uint32_t TotalAllocated = 0; // 总共分配的内存uint32_t TotalFreed = 0; // 总共释放的内存uint32_t CurrentUsage() { return TotalAllocated - TotalFreed; }
};static AllocationMetrics s_AllocationMetrics; // 静态实例void* operator new(size_t size)
{s_AllocationMetrics.TotalAllocated += size;return malloc(size);
}void operator delete(void* memory, size_t size)
{s_AllocationMetrics.TotalFreed -= size;free(memory);
}static void PrintMemoryUsage()
{std::cout << "Memory Usage: " << s_AllocationMetrics.CurrentUsage() << " bytes\n";
}

现在你可以随时随地查看分配了多少内存 只需要调用PrintMemoryUsage();

http://www.jsqmd.com/news/181297/

相关文章:

  • 【PyWebIO表格数据展示秘籍】:5步实现高效数据可视化,告别繁琐前端代码
  • 从零到精通:NiceGUI按钮事件绑定,你必须掌握的8种场景
  • 树形结构遍历性能优化,资深架构师20年总结的3大黄金法则
  • 售后案例 Intent phase 打通
  • Gradio文本生成交互全攻略(从入门到高阶部署)
  • Exo插件开发终极指南:如何快速构建个性化AI集群生态系统
  • 数据标注质量控制方法论:构建精准高效的标注管理体系
  • Jukebox AI音乐生成完整实战指南:从零基础到专业创作
  • 2025必备!MBA毕业论文必备的8个AI论文平台深度测评
  • RuoYi-AI MCP支持终极指南:从协议原理到实战应用
  • Fluent UI表单编排艺术:从零构建企业级动态表单系统
  • PyWebIO表格渲染技巧:3种方法让你的数据展示效率提升10倍
  • Labelme标注到VOC数据集:从标注困境到高效转换的实战指南
  • AppSmith零代码开发完整指南:快速构建企业级应用界面
  • AI取数技术终极指南:让自然语言成为你的数据查询利器
  • Exo框架:用普通设备搭建高性能AI集群的完整指南
  • Qwen3-4B大模型实战指南:5个步骤快速搭建AI应用
  • 探索语音合成技术助力残障人士信息获取平等
  • 响应格式不统一?FastAPI这样定制,团队开发效率提升80%
  • MechJeb2飞行助手:轻松掌握KSP太空航行自动化
  • PostfixAdmin邮件服务器管理终极指南:从部署到精通
  • 小米MiMo-Audio-7B-Instruct:音频智能的终极突破与5大创新实践
  • Windows也能秒开苹果HEIC照片:QuickLook完美解码指南
  • 小白羊网盘为何成为阿里云盘用户的首选?深度解析其独特优势
  • 分布式系统性能优化:Quickwit gRPC Gossip协议深度重构实践
  • darktable完全指南:免费开源RAW照片处理终极解决方案
  • 3步掌握Flutter与iOS原生界面混合开发:从零到精通实战指南
  • Spring Cloud微服务权限控制实战:MethodSecurity注解深度应用指南
  • SkyWalking与Prometheus数据打通实战指南:从零构建企业级监控体系
  • VoxCPM-1.5-TTS-WEB-UI支持的音频格式导出选项说明