简单的C实现用于跟踪内存的malloc/free?

12

编程语言:C 平台:ARM 编译器:ADS 1.2

我需要跟踪项目中简单的melloc/free调用。我只需要获取一个很基本的概念,即当程序分配了所有资源时需要多少堆内存。因此,我提供了malloc/free调用的包装器。在这些包装器中,当调用malloc时,我需要增加当前内存计数,并在调用free时将其减少。对于malloc情况来说很直接,因为我有从调用者那里分配的大小。但我想知道如何处理free情况,因为我需要在某个地方存储指针/大小映射。由于这是 C,我没有标准的映射可以轻易实现。

我试图避免链接任何库,因此更喜欢 *.c/h 实现。

所以我想知道是否已经有了一个简单的实现可以引导我。如果没有,这就是动力去实现它。

编辑:仅用于调试,此代码不与产品一起发布。

编辑:初始实现基于 Makis 的答案。我会感激您对此提出的反馈。

编辑:重新设计的实现。

#include <stdlib.h>
#include <stdio.h>
#include <assert.h>
#include <string.h>
#include <limits.h>

static size_t gnCurrentMemory = 0;
static size_t gnPeakMemory    = 0;

void *MemAlloc (size_t nSize)
{
  void *pMem = malloc(sizeof(size_t) + nSize);

  if (pMem)
  {
    size_t *pSize = (size_t *)pMem;

    memcpy(pSize, &nSize, sizeof(nSize));

    gnCurrentMemory += nSize;

    if (gnCurrentMemory > gnPeakMemory)
    {
      gnPeakMemory = gnCurrentMemory;
    }

    printf("PMemAlloc (%#X) - Size (%d), Current (%d), Peak (%d)\n",
           pSize + 1, nSize, gnCurrentMemory, gnPeakMemory);

    return(pSize + 1);
  }

  return NULL;
}

void  MemFree (void *pMem)
{
  if(pMem)
  {
    size_t *pSize = (size_t *)pMem;

    // Get the size
    --pSize;

    assert(gnCurrentMemory >= *pSize);

    printf("PMemFree (%#X) - Size (%d), Current (%d), Peak (%d)\n",
           pMem,  *pSize, gnCurrentMemory, gnPeakMemory);

    gnCurrentMemory -= *pSize;

    free(pSize);
  }
}

#define BUFFERSIZE (1024*1024)

typedef struct
{
  bool flag;
  int buffer[BUFFERSIZE];
  bool bools[BUFFERSIZE];
} sample_buffer;

typedef struct
{
  unsigned int whichbuffer;
  char ch;
} buffer_info;


int main(void)
{
  unsigned int i;
  buffer_info *bufferinfo;

  sample_buffer  *mybuffer;

  char *pCh;

  printf("Tesint MemAlloc - MemFree\n");

  mybuffer = (sample_buffer *) MemAlloc(sizeof(sample_buffer));

  if (mybuffer == NULL)
  {
    printf("ERROR ALLOCATING mybuffer\n");

    return EXIT_FAILURE;
  }

  bufferinfo = (buffer_info *) MemAlloc(sizeof(buffer_info));

  if (bufferinfo == NULL)
  {
    printf("ERROR ALLOCATING bufferinfo\n");

    MemFree(mybuffer);

    return EXIT_FAILURE;
  }

  pCh = (char *)MemAlloc(sizeof(char));

  printf("finished malloc\n");

  // fill allocated memory with integers and read back some values
  for(i = 0; i < BUFFERSIZE; ++i)
  {
    mybuffer->buffer[i] = i;
    mybuffer->bools[i] = true;
    bufferinfo->whichbuffer = (unsigned int)(i/100);
  }


  MemFree(bufferinfo);
  MemFree(mybuffer);

  if(pCh)
  {
    MemFree(pCh);
  }

  return EXIT_SUCCESS;
}

我认为在MemAlloc中你不需要两个malloc()。只需编写一个宏来确定对齐的良好大小(或使用64位,我认为对于每种情况都足够了),然后在分配内存之前将nSize增加该数量即可。 - Makis
谢谢Makis。我在32位平台上。我已经更新了我的实现,使用单个malloc在MemAlloc中。不过,我不明白对齐的意义。如果可以的话,你能指出在我的实现中可能存在问题的地方吗?假设,如果传递给MemFree或从malloc返回的指针已经不对齐,那么如果我没有使用我的包装器,这些指针本来就是不对齐的,对吗? - dubnde
1
这里有一个关于这个问题的不错的解释: http://www.goingware.com/tips/getting-started/alignment.html我也会将大小信息存储在32位中,这应该可以解决这个问题。问题可能是这样的:你从位置X开始保留内存,前两个字节是你的大小信息,所以你向调用者返回x+2。然而,如果对齐是4字节,你可能会遇到问题。因此,请检查size_t的大小,或者如果你想要可移植的代码,你需要定义一些宏。 - Makis
好的,谢谢。sizeof(size_t)为4个字节。实际上我会使用uint32_t来使其更明确。 - dubnde
7个回答

14

您可以在包装器中多分配一些字节,并放置id(如果要将malloc()和free()耦合)或仅放置大小。只需malloc()更多的内存,将信息存储在内存块的开头,并将返回的指针向前移动相同数量的字节。

顺便说一下,这也可以很容易地用于fence指针/指纹等。


4
请确保你返回的指针具有与malloc返回的指针相同或更好的对齐方式,以防万一。 - Eyal
不错的观点,Eyal。无论我因对齐错误(通常是结构体)而受到多少次伤害,我仍然倾向于忘记它 :) - Makis
1
大小可能已经存在,将返回的指针转换为(int *)并检查ptr[-1]或ptr[-2]处的值。请注意,分配的大小可能会四舍五入以保持对齐,因此请求123字节缓冲区可能会返回128字节,因此请相应地进行检查。 - TMN

2

1

你可以使用valgrind而不是自己实现。如果你不关心分配的内存量,你可以使用更简单的实现方式:(我很快地完成了这个,所以可能会有错误,我意识到这不是最有效的实现方式。pAllocedStorage应该给定一个初始大小,并增加一些因素进行调整等,但你明白我的意思。)

编辑:我错过了这是为ARM而设计的,据我所知,valgrind在ARM上不可用,所以这可能不是一个选择。

static size_t indexAllocedStorage = 0;
static size_t *pAllocedStorage = NULL;
static unsigned int free_calls = 0; 
static unsigned long long int total_mem_alloced = 0; 

void * 
my_malloc(size_t size){
    size_t *temp;
    void *p = malloc(size);
    if(p == NULL){
    fprintf(stderr,"my_malloc malloc failed, %s", strerror(errno));
    exit(EXIT_FAILURE);
    }

    total_mem_alloced += size;

    temp = (size_t *)realloc(pAllocedStorage, (indexAllocedStorage+1) * sizeof(size_t));
    if(temp == NULL){
        fprintf(stderr,"my_malloc realloc failed, %s", strerror(errno));
         exit(EXIT_FAILURE);
    }

    pAllocedStorage = temp; 
    pAllocedStorage[indexAllocedStorage++] = (size_t)p;

    return p;
}

void 
my_free(void *p){
    size_t i;
    int found = 0;

    for(i = 0; i < indexAllocedStorage; i++){
    if(pAllocedStorage[i] == (size_t)p){
        pAllocedStorage[i] = (size_t)NULL;
        found = 1;
        break;
        }
    }

    if(!found){
        printf("Free Called on unknown\n");
    }

    free_calls++;
    free(p);
}

void 
free_check(void) {
    size_t i;

    printf("checking freed memeory\n");
    for(i = 0; i < indexAllocedStorage; i++){   
        if(pAllocedStorage[i] != (size_t)NULL){
            printf( "Memory leak %X\n", (unsigned int)pAllocedStorage[i]);
            free((void *)pAllocedStorage[i]);
        }
    }

    free(pAllocedStorage);
    pAllocedStorage = NULL;
}

1

我会使用rmalloc。它是一个简单的库(实际上只有两个文件),用于调试内存使用情况,但它也支持统计功能。由于您已经包装了函数,因此使用rmalloc应该非常容易。请记住,您还需要替换strdup等函数。


0

你的程序可能还需要拦截realloc()、calloc()、getcwd()(因为在某些实现中,当缓冲区为NULL时,它可能会分配内存),以及可能是strdup()或类似函数,如果你的编译器支持的话。


1
如果你重写了malloc()函数(以及realloc(),calloc(),alloca()和free()),那么其他函数也会随之更新(这样说来),因为它们都在内部调用malloc()。 - TMN
我的程序只使用MemAlloc和MemFree,因此仅拦截malloc和free就足以满足此目的。 - dubnde

0

如果你正在运行 x86,你可以直接在 valgrind 下运行你的二进制文件,它会使用标准实现的 mallocfree 来收集所有这些信息。简单易行。


0

我一直在尝试这个页面提到的一些相同的技术,并从谷歌搜索中进入了这里。我知道这个问题很老,但是想要为记录添加以下信息...

1)您的操作系统是否没有提供任何工具来查看正在运行的进程中使用了多少堆内存?我看到你在讨论ARM,所以这可能是情况。在大多数功能全面的操作系统中,这只是使用命令行工具查看堆大小的问题。

2)如果在您的libc中可用,在大多数平台上sbrk(0)将告诉您数据段的结束地址。如果你有它,你所需要做的就是将那个地址存储在你程序的开头(比如说,startBrk=sbrk(0)),然后在任何时候你分配的大小是sbrk(0) - startBrk。

3) 如果可以使用共享对象,您正在动态链接到libc,并且您的操作系统运行时加载器具有类似于LD_PRELOAD环境变量的东西,则可能会发现构建自己的共享对象更有用,该共享对象定义了具有相同符号(malloc(),而不是MemAlloc())的实际libc函数,然后让加载器首先加载您的库并“插入”libc函数。 您可以使用dlsym()和RTLD_NEXT标志进一步获取实际libc函数的地址,因此您可以执行上面所做的操作,而无需重新编译所有代码以使用malloc / free包装器。 然后,当您启动程序(或任何符合第一句描述的程序)时,只是一个运行时决策,您设置像LD_PRELOAD = mymemdebug.so这样的环境变量,然后运行它。(搜索共享对象插入..这是一种很棒的技术,许多调试器/分析工具都在使用)


这是一个非常简单的内部操作系统。1)没有(2)不可用的sbrk(3)不可用的共享对象。 - dubnde

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接