【问题标题】:Why no memory leak?为什么没有内存泄漏?
【发布时间】:2011-03-05 19:30:21
【问题描述】:

以下内容旨在获取一个可变长度的常量 char 并以良好的格式打印出来以进行日志记录。我相信读者会对如何改进这一点提出建议,我很欢迎。

让我感到困惑的是,我预计每次调用 ToHexString() 时都需要 free() 返回的静态字符。相反,我没有看到任何内存泄漏。即使我使用内联函数,因此也不会将其返回值分配给变量。

我创建了一个简单的测试,在循环中调用此函数,每次都使用不同长度的 cString 和 nMaxChars 参数。然后我查看了虚拟机状态。我的测试程序的内存分配和可用内存从未改变。

在我看来,每次调用 malloc 并且没有空闲时它应该增加。

static char *ToHexString(const char *cString,int nMaxChars)
{
    static char *cStr;



    /*if (80>strlen(cString))
        nRawChars=strlen(cString);
    if (nMaxChars>nRawChars)
        nRawChars=nMaxChars;
    */
    if (nMaxChars==0)
        nMaxChars=80;

    printf("There are %i chars\n",nMaxChars);

    char *cStr1;
    char *cStr2;
    char *cStr3;
    int nLen=nMaxChars*6;
    cStr=calloc(nLen,sizeof(char));

    cStr1=calloc(10,sizeof(char));
    cStr2=calloc(nLen,sizeof(char));
    cStr3=calloc(nLen,sizeof(char));
    cStr1[0]='\0';
    cStr2[0]='\0';
    cStr3[0]='\0';
    int nC1=0;
    int nRowCnt=0;

    for (nC1=0;nC1<nMaxChars;nC1++)
    {
        ++nRowCnt;
        if (cString[nC1]==0x00)
            snprintf(cStr1,8,"[00] ");
        else
            snprintf(cStr1,8,"[%02x] ",(unsigned char)cString[nC1]);

        if ( (nRowCnt%8==0) )
        {
            snprintf(cStr3,nLen,"%s%s\n",cStr2,cStr1);
        }
        else
            snprintf(cStr3,nLen,"%s%s",cStr2,cStr1);

        snprintf(cStr2,nLen,"%s",cStr3);
    }
    snprintf(cStr,nLen,"%s",cStr3);
    free(cStr1);
    free(cStr2);
    free(cStr3);
    return(cStr);
}

这里是调用例程:

for (i=0;i<100;i++)
{
    memset(&cBuff, 0,255);
    printf("Reading %s now..\n",cPort);
    while (sleep(1)==-1);
    nChars=read(nPort, cBuff, 255);
    //printf("Read %i chars from %s\n",nChars,cPort);
    if (nChars<=0)
        printf("Read 0 chars from %s\n",cPort);
    else
        printf("Read %i chars from %s\n%s\n",nChars,cPort,ToHexString(cBuff,nChars));
}

【问题讨论】:

  • 您的分辨率可能太小了。您的操作系统可能会分配 4-8k 页。除非你释放它,否则你确实有泄漏,你可能只是看不到它,因为这些分配很小。
  • @Jason - 你可能是对的。我认为静态指针会保留相同的地址(如果他只调用 calloc 一次然后随后调用 realloc,它会保留)。
  • 通过 valgrind 运行该代码,您会看到泄漏。
  • 用大字符串运行 100,000 甚至 1,000,000 次
  • @Tim Post:由于静态作用域,指针本身的地址保持不变,但它指向的已分配块的地址随着函数的每次调用而改变,然后被丢弃重新分配时的下一次调用。他/应该/第一次分配一个块,然后如果最大长度发生变化,则重新分配,但他不是:)

标签: c memory-leaks free malloc


【解决方案1】:

以下是泄漏:

static void memeat(void)
{
        static char *foo = NULL;

        foo = malloc(1024);

        return;

}

Valgrind 输出:

==16167== LEAK SUMMARY:
==16167==    definitely lost: 4,096 bytes in 4 blocks
==16167==    indirectly lost: 0 bytes in 0 blocks
==16167==      possibly lost: 0 bytes in 0 blocks
==16167==    still reachable: 1,024 bytes in 1 blocks
==16167==         suppressed: 0 bytes in 0 blocks
==16167== Rerun with --leak-check=full to see details of leaked memory

注意,still reachable(1024 字节)是上次输入 memeat() 的结果。静态指针仍然持有对程序退出时分配的最后一个块memeat() 的有效引用。只是不是以前的块。

以下内容不是泄漏:

static void memeat(void)
{
        static char *foo = NULL;

        foo = realloc(foo, 1024);

        return;

}

Valgrind 输出:

==16244== LEAK SUMMARY:
==16244==    definitely lost: 0 bytes in 0 blocks
==16244==    indirectly lost: 0 bytes in 0 blocks
==16244==      possibly lost: 0 bytes in 0 blocks
==16244==    still reachable: 1,024 bytes in 1 blocks
==16244==         suppressed: 0 bytes in 0 blocks
==16244== Rerun with --leak-check=full to see details of leaked memory

这里foo指向的地址已经被释放,foo现在指向新分配的地址,下次进入memeat()时会继续这样做。

说明:

static 存储类型表示指针foo 将指向与每次进入函数时初始化的地址相同的地址。但是,如果您在每次通过malloc()calloc() 输入函数时更改该地址,则您丢失了对先前分配的块的引用。因此,泄漏,因为任何一个都将返回一个新地址。

valgrind 中的'Still Reachable' 意味着所有分配的堆块仍然有一个有效的指针,可以在退出时访问/操作/释放它们。这类似于在main() 中分配内存而不释放它,只是依靠操作系统来回收内存。

简而言之,是的 - 你有泄漏。但是,您可以很容易地修复它。请注意,您确实依赖于您的操作系统来回收内存,除非您向函数添加另一个参数,该参数只是告诉 ToHexString 在静态指针上调用 free ,您可以在退出时使用它。

与此类似:(完整的测试程序)

#include <stdlib.h>

static void memeat(unsigned int dofree)
{
        static char *foo = NULL;

        if (dofree == 1 && foo != NULL) {
                free(foo);
                return;
        }

        foo = realloc(foo, 1024);

        return;

}


int main(void)
{
        unsigned int i;

        for (i = 0; i < 5; i ++)
                memeat(0);

        memeat(1);
        return 0;
}

Valgrind 输出:

==16285== HEAP SUMMARY:
==16285==     in use at exit: 0 bytes in 0 blocks
==16285==   total heap usage: 6 allocs, 6 frees, 6,144 bytes allocated
==16285==
==16285== All heap blocks were freed -- no leaks are possible

关于最终输出的说明

是的,根据malloc() 在程序运行时返回的内容实际分配了 6144 个字节,但这只是意味着释放了静态指针,然后根据输入 memeat() 的次数重新分配。在任何给定时间,程序的实际堆使用实际上只是 2*1024, 1k 来分配新指针,而旧指针仍然存在等待复制到新指针。

同样,调整代码应该不会太难,但我不清楚为什么要使用静态存储开始。

【讨论】:

  • Tim,如果我采用你建议的免费技术(memeat(1)),那么是否需要 realloc()?我使用静态,因为我有过普通 char *var 被破坏的经验。
  • @k1mgy - 是的,或者如果静态指针不为空,则需要释放然后 calloc() 静态指针。否则,您将失去对上次输入函数时分配的内容的引用。虽然您不需要显式释放在退出时仍可到达的块,但这样做仍然是一种很好的做法,这意味着在退出之前释放静态指针的一些方法将是理想的。
  • @k1mgy - 另外,如果你在输入函数时走“如果非空则免费”路线,请务必在每次释放静态指针时初始化它,否则你可能仍然会结束由于野指针而泄漏。
  • @k1mgy - 我仍然不明白您为什么要使用静态存储。如果您要重构,也许只是让它返回一个指向调用者可以释放的已分配块的指针,正如 tommieb75 所建议的那样?我回答了你关于泄漏的问题的细节......但实施似乎仍然存在问题。或者,如果你知道字符串总是那么小......像static char retstring[1024]
  • @k1mgy:我必须同意蒂姆的观点,因为这些微优化“技巧”可能会适得其反……先让它工作,然后离开优化(如果有的话)稍后,因为通过优化,代码可能/可能会中断......在处理这类事情时,你的工具箱中总是有 valgrind 和夹板,并且要成为一个更快乐的程序员,拥有那些你可以消除这种微妙之处的工具在这样的代码中......:D
【解决方案2】:

我想指出我在检查代码时想到的两件事:

cStr=calloc(nLen,sizeof(char));

您为什么不对此进行错误检查....正如我从代码中看到的那样,假设内存将始终可用,则检查为零....危险.... 总是 检查 NULL 从内存分配函数调用返回时的指针,例如 callocmallocrealloc...它将总是程序员有责任管理free'ing 指针以将它们返回到堆中。

另外,因为您已将 cStr 声明为指向 char * 的静态指针,所以您根本没有释放它,事实上这一行证明了这一点:

printf("从 %s\n%s\n 读取 %i 个字符",nChars,cPort,ToHexString(cBuff,nChars)); ^^^^^^^^^^^^

你最好这样做:

字符 *hexedString; hexedString = ToHexString(cBuff, nChars); …… printf("从 %s\n%s\n 读取 %i 个字符",nChars,cPort,hexedString); …… 免费(十六进制字符串);

【讨论】:

  • 感谢 tommieb75。我对 malloc 和 calloc 相当随意。没有了:)
【解决方案3】:

这是内存泄漏。如果您持续调用该函数,则程序使用的内存会增加。例如:

int main() {
   while (1) {
      ToHexString("testing the function", 20);
   }
}

如果您运行此程序并使用系统监控工具观察该进程,您会发现已使用的内存在不断增加。

泄漏在您的程序中可能并不明显,因为该函数每次调用仅泄漏几个字节并且不经常调用。所以内存使用量的增加并不是很明显。

【讨论】:

    【解决方案4】:

    您将例程的结果返回到一个新数组中。对于您的模型,使用结果释放此数组的责任在于调用者。因此,在调用者中,您应该将例程的结果存储在一个临时文件中,对它做任何您想做的事情,然后在最后释放它。

    【讨论】:

      猜你喜欢
      • 2011-02-01
      • 1970-01-01
      • 2012-05-02
      • 1970-01-01
      • 2011-10-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-04-30
      相关资源
      最近更新 更多