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

时间:2019-05-02 17:25:25

标签: c memory-management memory-leaks valgrind

我正在用C实现带有链接列表的优先级队列,但是当我打印出pop操作时却遇到了内存泄漏。我还有另一个内存泄漏,我也试图找到它。

请注意,我使用heapusage代替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);                                                                          

可能不是 my 代码引起的,可能是内存检查器。以下程序泄漏了1个块,堆使用了2个alloc和4个free。

#include <stdio.h>

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

2 个答案:

答案 0 :(得分:8)

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

我创建了文件ERROR: HHH000346: Error during managed flush [Found shared references to a collection: AddressSet.value.addressSet] org.hibernate.HibernateException: Found shared references to a collection: AddressSet.value.addressSet at org.hibernate.engine.internal.Collections.processReachableCollection(Collections.java:182) at org.hibernate.event.internal.FlushVisitor.processCollection(FlushVisitor.java:42) at org.hibernate.event.internal.AbstractVisitor.processValue(AbstractVisitor.java:104) at org.hibernate.event.internal.AbstractVisitor.processValue(AbstractVisitor.java:65) at org.hibernate.event.internal.AbstractVisitor.processValues(AbstractVisitor.java:44) at org.hibernate.event.internal.AbstractVisitor.processComponent(AbstractVisitor.java:85) at org.hibernate.event.internal.AbstractVisitor.processValue(AbstractVisitor.java:110) at org.hibernate.event.internal.AbstractVisitor.processValue(AbstractVisitor.java:65) at org.hibernate.event.internal.AbstractVisitor.processEntityPropertyValues(AbstractVisitor.java:59) at org.hibernate.event.internal.DefaultFlushEntityEventListener.onFlushEntity(DefaultFlushEntityEventListener.java:155) at org.hibernate.event.internal.AbstractFlushingEventListener.flushEntities(AbstractFlushingEventListener.java:216) at org.hibernate.event.internal.AbstractFlushingEventListener.flushEverythingToExecutions(AbstractFlushingEventListener.java:85) at org.hibernate.event.internal.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:38) at org.hibernate.internal.SessionImpl.flush(SessionImpl.java:1295) at org.hibernate.internal.SessionImpl.managedFlush(SessionImpl.java:468) at org.hibernate.internal.SessionImpl.flushBeforeTransactionCompletion(SessionImpl.java:3135) at org.hibernate.internal.SessionImpl.beforeTransactionCompletion(SessionImpl.java:2352) at org.hibernate.engine.jdbc.internal.JdbcCoordinatorImpl.beforeTransactionCompletion(JdbcCoordinatorImpl.java:491) at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl.beforeCompletionCallback(JdbcResourceLocalTransactionCoordinatorImpl.java:147) at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl.access$100(JdbcResourceLocalTransactionCoordinatorImpl.java:38) at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl$TransactionDriverControlImpl.commit(JdbcResourceLocalTransactionCoordinatorImpl.java:231) at org.hibernate.engine.transaction.internal.TransactionImpl.commit(TransactionImpl.java:65) at com.ervacon.bitemporal.HibernateTest.testPersistence(HibernateTest.java:77) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50) at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47) at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17) at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26) at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325) at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78) at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57) at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290) at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71) at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288) at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58) at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268) at org.junit.runners.ParentRunner.run(ParentRunner.java:363) at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:86) at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:459) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:678) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:382) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:192)

test.c

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

$ 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)

堆满了,漏了!

$ 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== 

分析

通过挂接malloc和free(并且不扫描内存中的指针)来进行堆工作。在文档中,堆没有充分说明此方法的优缺点。一个优点是它速度快,但缺点是它不精确。

特别是,我会把堆放当作错误信息来称呼:“绝对丢失”一词在这里不适用!

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

总的来说,我还想提醒人们使用这些工具,误报是生活中的事实。程序是否“ Valgrind clean”与程序是否正确是一个不同的问题。

答案 1 :(得分:3)

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

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

#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()函数之前都应已关闭。关闭流将在后台释放为其分配的所有内存。