文章506
标签266
分类65

简单实现C++内存分配跟踪

有的时候我们想要跟踪我们的代码到底分配了多少的内存,一个常用的方法是使用 Valgrind 工具进行内存分析;

但是对于一些场景,我们不想这么麻烦,那么此时我们可以通过简单的覆盖 malloc、free 等函数实现!

源代码:


简单实现C++内存分配跟踪

简单实现

我们知道,C、C++ 中主要是通过 mallocfree 函数以及 newdeletenew[]delete[] 等运算符进行内存分配;

对于 mallocfree 函数而言:

虽然他们是在编译时通过链接到 glibc 库加入的,但是我们可以通过宏定义来替换他们,从而实现自己的逻辑;

对于 newdelete 等运算符,可以通过全局覆盖(override)他们来实现 Hook;

具体实现的代码很简单,如下:

track_memory.h

#ifndef TRACK_MEMORY_H_
#define TRACK_MEMORY_H_

/**
 * Implements simple memory tracking for use in C++ applications.
 * Define: TRACK_MEMORY to enable during debug and unit testing, and undef for production.
 * Define: PRINT_MEMORY_TRACKING to print memory tracking
 */

#include <cassert>
#include <cstdio>
#include <cstdlib>

#ifdef TRACK_MEMORY
#include <map>

size_t gAllocatedMemory = 0;
bool gTrackAllocation = true;
typedef std::map<void *, size_t> AllocationMap;
static AllocationMap gAllocationMapStandard;
static AllocationMap gAllocationMapArray;

/**
 * Allocates using a map to keep track of sizes.
 */
inline void *wrap_malloc(size_t size,
                         const char *file,
                         int line,
                         const char *func,
                         AllocationMap &map = gAllocationMapStandard) {

  assert(size != 0);
  void *ptr;

  if (gTrackAllocation) {
    gAllocatedMemory += size;
    ptr = malloc(size);

    gTrackAllocation = false;
    map[ptr] = size;
    gTrackAllocation = true;

#ifdef PRINT_MEMORY_TRACKING
    printf("[Malloc %s:%d:%s] Allocated mem 0x%8.8lx: %8ld (%ld)\n",
           file, line, func, (unsigned long) ptr, gAllocatedMemory, size);
#endif
  } else {
    ptr = malloc(size);
  }

  if (ptr == nullptr)
    throw std::bad_alloc();
  else
    return ptr;
}

/**
 * Deletes stuff allocated with tracked_new.
 */
inline void wrap_free(void *ptr,
                      const char *file,
                      int line,
                      const char *func,
                      AllocationMap &map = gAllocationMapStandard) {

  if (gTrackAllocation) {
    size_t size = map[ptr];
    assert(size != 0);
    gAllocatedMemory -= size;

#ifdef PRINT_MEMORY_TRACKING
    printf("[Delete %s:%d:%s] Deallocated mem 0x%8.8lx: %8ld (-%ld)\n",
           file, line, func, (unsigned long) ptr,
           gAllocatedMemory,
           size);
#endif

    gTrackAllocation = false;
    map.erase(ptr);
    gTrackAllocation = true;
  }

  free(ptr);
}

#define malloc(X) wrap_malloc(X, __FILE__, __LINE__, __FUNCTION__)
#define free(X) wrap_free(X, __FILE__, __LINE__, __FUNCTION__)

void *operator new(size_t size) noexcept(false) {
  return wrap_malloc(size, __FILE__, __LINE__, __FUNCTION__, gAllocationMapStandard);
}

void *operator new[](size_t size) noexcept(false) {
  return wrap_malloc(size, __FILE__, __LINE__, __FUNCTION__, gAllocationMapArray);
}

void operator delete(void *ptr) noexcept {
  wrap_free(ptr, __FILE__, __LINE__, __FUNCTION__, gAllocationMapStandard);
}

void operator delete[](void *ptr) noexcept {
  wrap_free(ptr, __FILE__, __LINE__, __FUNCTION__, gAllocationMapArray);
}

#endif /* TRACK_MEMORY */

#endif // TRACK_MEMORY_H_

为了使用起来更加方便,我定义了:

  • TRACK_MEMORY:启用内存监控的总开关;
  • PRINT_MEMORY_TRACKING:是否打印内存分配内容;

同时,定义了:gAllocationMapStandardgAllocationMapArray 来保存分配的内存空间;

并且使用 gAllocatedMemory 来记录当前已分配的字节数;


覆盖mallocfree

代码中,wrap_mallocwrap_free 包装了在 glibc 中的 mallocfree 函数;

随后,通过宏覆盖了原来的 mallocfree

#define malloc(X) wrap_malloc(X, __FILE__, __LINE__, __FUNCTION__)
#define free(X) wrap_free(X, __FILE__, __LINE__, __FUNCTION__)

注意:这里必须要先定义函数,再进行宏替换,否则会将 wrapper 函数中的 malloc 也给替换掉!

这里实现的是单线程的内存分配跟踪,对于多线程的实现,需要使用 Lock 将上面共享的内容保护起来!


覆盖newdelete

上面的代码完成了对 mallocfree 函数的覆盖;

那么对于 newdelete 等运算符来说,我们只需要调用上面已经实现的包装函数即可:

void *operator new(size_t size) noexcept(false) {
  return wrap_malloc(size, __FILE__, __LINE__, __FUNCTION__, gAllocationMapStandard);
}

void *operator new[](size_t size) noexcept(false) {
  return wrap_malloc(size, __FILE__, __LINE__, __FUNCTION__, gAllocationMapArray);
}

void operator delete(void *ptr) noexcept {
  wrap_free(ptr, __FILE__, __LINE__, __FUNCTION__, gAllocationMapStandard);
}

void operator delete[](void *ptr) noexcept {
  wrap_free(ptr, __FILE__, __LINE__, __FUNCTION__, gAllocationMapArray);
}

这里需要注意的是:

由于运算符重载函数是无法 inline 的,因此 __LINE____FILE__ 实际上只能获取到这个文件的路径;

并且,由于我们在重载时,无法实现在和原来的 void *operator new(size_t size) 函数签名完全相同下、增加入参的重载(否则会不知道究竟使用哪个实现!)

如果有小伙伴们知道怎么做,可以在下方留言评论~

关于重写库函数或系统调用:

至此,我们的功能编写完成!


测试用例

在使用时,我们在源代码的开头引入这个头文件,并定义对应的宏开启功能即可!

即:

#define TRACK_MEMORY
#define PRINT_MEMORY_TRACKING

#include "track_memory.h"

例如:

main.cc

#define TRACK_MEMORY
#define PRINT_MEMORY_TRACKING

#include "track_memory.h"

int main() {
  char *buf = new char[100];
  delete[] buf;

  struct TestStruct {
    long a;
    long b;
  };

  auto *testStruct = new TestStruct;
  delete testStruct;

  auto *ptr = malloc(sizeof(TestStruct));
  free(ptr);
}

执行后输出:

[Malloc /root/data/cpp-learn/track_memory.h:94:operator new []] Allocated mem 0x01b0f260:      100 (100)
[Delete /root/data/cpp-learn/track_memory.h:102:operator delete []] Deallocated mem 0x01b0f260:        0 (-100)
[Malloc /root/data/cpp-learn/track_memory.h:90:operator new] Allocated mem 0x01b0f720:       16 (16)
[Delete /root/data/cpp-learn/track_memory.h:98:operator delete] Deallocated mem 0x01b0f720:        0 (-16)
[Malloc /root/data/cpp-learn/main.cc:18:main] Allocated mem 0x01b0f720:       16 (16)
[Delete /root/data/cpp-learn/main.cc:19:main] Deallocated mem 0x01b0f720:        0 (-16)

即可完成对内存分配的跟踪,非常方便!


附录

源代码:

文章参考:



本文作者:Jasonkay
本文链接:https://jasonkayzk.github.io/2022/11/12/简单实现C-内存分配跟踪/
版权声明:本文采用 CC BY-NC-SA 3.0 CN 协议进行许可