2017-02-09 9 views
4

Я стараюсь избегать ложных срабатываний с valgrind, но я сосать с комбинацией atexit() и fork(), несмотря на то, что использовал --trace-children=yes. Мой код:valgrind --trace-children = yes сообщает утечку несмотря на очистку atexit

#include <stdio.h> 
#include <unistd.h> 
#include <stdlib.h> 

static int * arr; 

static void cleanup() { 
    free(arr); 
    printf("free arr as: %p\n", (void *)arr); 
} 

int main() 
{ 
    arr = malloc(16 * sizeof(int)); 
    printf("allocated arr as: %p\n", (void *)arr); 
    atexit(cleanup); 

    pid_t pid = fork(); 
    if (pid == -1) { 
     exit(1); 
    } else if (pid == 0) { 
     // child 
     _exit(0); 
    } else { 
     // parent 
     exit(0); 
    } 
} 

командной строки:

$ clang -Weverything leak.c 
$ valgrind --trace-children=yes ./a.out 
==3287== Memcheck, a memory error detector 
==3287== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al. 
==3287== Using Valgrind-3.12.0 and LibVEX; rerun with -h for copyright info 
==3287== Command: ./a.out 
==3287== 
allocated arr as: 0x5202040 
free arr as: 0x5202040 
==3288== 
==3288== HEAP SUMMARY: 
==3288==  in use at exit: 64 bytes in 1 blocks 
==3288== total heap usage: 2 allocs, 1 frees, 1,088 bytes allocated 
==3288== 
==3288== LEAK SUMMARY: 
==3288== definitely lost: 0 bytes in 0 blocks 
==3288== indirectly lost: 0 bytes in 0 blocks 
==3288==  possibly lost: 0 bytes in 0 blocks 
==3288== still reachable: 64 bytes in 1 blocks 
==3288==   suppressed: 0 bytes in 0 blocks 
==3288== Rerun with --leak-check=full to see details of leaked memory 
==3288== 
==3288== For counts of detected and suppressed errors, rerun with: -v 
==3288== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0) 
==3287== 
==3287== HEAP SUMMARY: 
==3287==  in use at exit: 0 bytes in 0 blocks 
==3287== total heap usage: 2 allocs, 2 frees, 1,088 bytes allocated 
==3287== 
==3287== All heap blocks were freed -- no leaks are possible 
==3287== 
==3287== For counts of detected and suppressed errors, rerun with: -v 
==3287== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0) 

Основываясь на printf() выходе, похоже, нет никаких утечек. Могу ли я убедить valgrind в этом, или я должен просто добавить это в мой файл подавления valgrind?

ответ

4

Основываясь на выходе printf(), похоже, что утечек нет. Могу ли я убедить valgrind в этом, или я должен просто добавить это в мой файл подавления valgrind?

Похоже, что valgrind прав. Если вы интерпретируете вывод printf() как указание на отсутствие утечек, то вы не оцениваете эффект fork().

Когда вы переделаете ребенка, он получает полную копию адресного пространства своего родителя. Обычно это реализуется с помощью страниц копирования на запись, но оно по-прежнему представляет собой память, принадлежащую ребенку. В вашем случае это включает в себя копию динамически распределенного массива arr.

Ребенок завершает свою работу, вызывая _exit(), поэтому, хотя он наследует регистрацию выходных обработчиков своего родителя, зарегистрированные обработчики выходов не вызывают в этом процессе. Вы можете сказать, что это так, потому что вы видите вывод cleanup() только один раз. В результате копия arr, принадлежащая ребенку, никогда не освобождается, как говорит вам Вальгринд.

Это немного педантично, чтобы назвать это утечкой памяти. Память, о которой идет речь, по-прежнему доступна, когда программа завершается, и в это время она восстанавливается системой. Это просто не явно освобожден до окончания.

+0

Спасибо! Да, я действительно неправильно понял «копию адресного пространства своих родителей» в разделе 'fork()'. Чтобы помочь любому, кто наткнется на этот вопрос в будущем, пожалуйста, Google «виртуальное адресное пространство» и/или прочитайте: http://stackoverflow.com/a/5365635/7541781 – gperciva

4

Вы используете _exit() в дочернем процессе. Согласно странице пользователя _exit:

The function _exit() is like exit(3), but does not call any functions registered with atexit(3) or on_exit(3). 

Измените его, чтобы выйти (0). Он должен работать.

+0

Спасибо! Я видел другую документацию о 'fork()', говорящей, что ребенок должен вызывать '_exit()', а не 'exit()' [1]. Однако ручное вызов 'cleanup()' внутри '// child' похоже работает. [1] «По техническим причинам здесь должна использоваться функция POSIX _exit вместо стандартной функции выхода C.)« https://en.wikipedia.org/wiki/Fork_(system_call)#Application_usage – gperciva

+0

@gperciva вы должны понимать * причины * рекомендаций, которые вы описываете. Первичный в этом конкретном случае - избегать вызова зарегистрированных обработчиков выходных данных, что часто желательно в раздвоенном дочернем процессе. В случае, если вы на самом деле * хотите * вызывать зарегистрированные обработчики выходных данных, как вам кажется, в этом случае вы, вероятно, просто захотите использовать 'exit()' вместо '_Exit()', как это предлагает этот ответ. –

+0

@gperciva, обратите внимание также, что хотя '_exit()' не является частью стандартной библиотеки C, стандартная библиотека имеет '_Exit()' с C99, что делает то же самое и немного более переносимо. –