heapusage 检测到可能由 printf 引起的内存泄漏

heapusage detects Memory leak possibly caused by printf

我正在用 C 语言的链表实现一个优先级队列,但是当我打印出 pop 操作时出现内存泄漏。我还有另一个内存泄漏问题,我也在努力寻找它。

作为旁注,我使用 heapusage by d99kris 而不是 Valgrind

这是我使用printf时的堆摘要:

HEAP SUMMARY:
in use at exit: 4112 bytes in 2 blocks
total heap usage: 10 allocs, 17 frees, 4536 bytes allocated
peak heap usage: 4256 bytes allocated
16 bytes in 1 block(s) are lost, originally allocated at:
LEAK SUMMARY:
definitely lost: 4112 bytes in 2 blocks

这是没有printf的堆摘要:

HEAP SUMMARY:
in use at exit: 16 bytes in 1 blocks
total heap usage: 9 allocs, 10 frees, 440 bytes allocated
peak heap usage: 256 bytes allocated
LEAK SUMMARY:
definitely lost: 16 bytes in 1 blocks

我的pop函数:

void *prio_q_pop(struct prio_q *q) {
     q->size--;
     struct elem *temp = q->first;
     (q->first) = (q->first)->next;
     void *asd = temp->datei;
     free(temp);
     return asd;
 }

还有我的 main 函数,我调用 printf

struct prio_q *queue;
     char *s;
     int i;

     queue = prio_q_create();

     push(queue, "Bye World", 0);

     for (i = 0; i < 5; i++) {
         s = prio_q_pop(queue);
         //printf("%s\n", s);
     }
     s = prio_q_front(queue);
     //printf("%s\n", s);                                                                          

reason

问题不是由 my 代码引起的,它是内存检查器。以下程序泄漏了 1 个块,堆使用了 2 次分配和 4 次释放。

#include <stdio.h>

int main() {
    printf("omer");
    return 0;
}

这是误报。如果有的话,问题是 heapusage 没有足够好的文档。我建议使用更好的泄漏检查器,如泄漏消毒剂或 Valgrind。

我创建了一个文件 test.c

#include <stdio.h>
int main(int argc, char **argv) {
    puts("Hello, world!");
}

使用泄漏消毒剂,没有错误。

$ cc -fsanitize=leak -g test.c
$ ./a.out 
Hello, world!

使用地址清理器,没有错误。

$ cc -fsanitize=address -g test.c
$ ./a.out 
Hello, world!

使用 Valgrind,没有错误。

$ cc -g test.c
$ valgrind ./a.out
==189174== Memcheck, a memory error detector
==189174== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==189174== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info
==189174== Command: ./a.out
==189174== 
Hello, world!
==189174== 
==189174== HEAP SUMMARY:
==189174==     in use at exit: 0 bytes in 0 blocks
==189174==   total heap usage: 1 allocs, 1 frees, 1,024 bytes allocated
==189174== 
==189174== All heap blocks were freed -- no leaks are possible
==189174== 
==189174== For counts of detected and suppressed errors, rerun with: -v
==189174== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

使用heapusage,泄漏!

$ cc -g test.c                  
$ ./heapusage ./a.out
Hello, world!
==189005== Heapusage - https://github.com/d99kris/heapusage
==189005== 
==189005== HEAP SUMMARY:
==189005==     in use at exit: 1024 bytes in 1 blocks
==189005==   total heap usage: 1 allocs, 0 frees, 1024 bytes allocated
==189005==    peak heap usage: 1024 bytes allocated
==189005== 
==189005== 1024 bytes in 1 block(s) are lost, originally allocated at:
==189005==    at 0x00007f99f0de56a7: malloc + 49
==189005==    at 0x00007f99f0a96a32: _IO_file_doallocate + 114
==189005==    at 0x00007f99f0aa4a46: _IO_doallocbuf + 70
==189005==    at 0x00007f99f0aa3da8: _IO_file_overflow + 472
==189005==    at 0x00007f99f0aa2e86: _IO_file_xsputn + 182
==189005==    at 0x00007f99f0a99033: _IO_puts + 211
==189005==    at 0x000055f667ee7655: 
==189005==    at 0x00007f99f0a502b1: __libc_start_main + 241
==189005==    at 0x000055f667ee755a: 
==189005== 
==189005== LEAK SUMMARY:
==189005==    definitely lost: 1024 bytes in 1 blocks
==189005== 

分析

Heapusage 通过挂钩 malloc 和 free 来工作(并且不扫描内存中的指针)。 Heapusage 没有在文档中完整解释这种方法的优点或缺点。优点是速度快,缺点是不精确

特别是,我认为 heapusage 给出的信息不正确:"definitely lost" 一词不适用于此处!

如果您想要更好的错误消息,请使用上面推荐的工具之一:泄漏消毒剂或 Valgrind (memcheck)。

总的来说,我还想提醒人们,误报是使用此类工具的常态。程序是否"Valgrind clean"与程序是否正确是不同的问题。

与 Valgrind 不同,heapusage 不会跟踪 C 库为其自身目的分配的内存。 printf 间接导致此问题,因为流 stdout 是行缓冲到终端并完全缓冲到文件。仅当您实际产生输出时才分配流缓冲区(通过 printf 或任何其他输出函数)。

您可以尝试通过在 main 函数开始时使 stdout 无缓冲来解决此限制。例如试试这个:

#include <stdio.h>

int main() {
    setvbuf(stdout, NULL, _IONBF, 0);
    printf("omer\n");
    return 0;
}

如果以上代码仍然显示泄漏,请尝试以下替代方法:

#include <stdio.h>

int main() {
    char buf[BUFSIZ];
    setvbuf(stdout, buf, _IONBF, BUFSIZ);
    printf("omer\n");
    fclose(stdout);
    return 0;
}

另请注意,从 stdin 读取输入也会为输入流分配一个缓冲区。程序打开的任何其他流都应该在离开 main() 函数之前关闭。关闭流会释放在后台为其分配的所有内存。