valgrind --trace-children=yes сообщает об утечке, несмотря на очистку atexit

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

#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?


person gperciva    schedule 09.02.2017    source источник


Ответы (2)


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

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

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

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

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

person John Bollinger    schedule 09.02.2017
comment
Спасибо! Да, я действительно неправильно понял копию родительской части адресного пространства fork(). Чтобы помочь любому, кто столкнется с этим вопросом в будущем, пожалуйста, поищите виртуальное адресное пространство Google и/или прочитайте: stackoverflow.com/a/5365635/ 7541781 - person gperciva; 09.02.2017

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

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

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

person ReddyVeeru    schedule 09.02.2017
comment
Спасибо! Я видел другую документацию о fork(), в которой говорилось, что дочерний элемент должен вызывать _exit(), а не exit()[1]. Однако ручной вызов cleanup() внутри // child, кажется, работает. [1] По техническим причинам здесь должна использоваться функция _exit POSIX вместо стандартной функции выхода C.) en.wikipedia.org/wiki/Fork_(system_call)#Application_usage - person gperciva; 09.02.2017
comment
@gperciva, вы должны понимать причины таких рекомендаций, как вы описываете. Первичным в этом конкретном случае является избежание вызова зарегистрированных обработчиков выхода, что часто желательно в разветвленном дочернем процессе. В случае, если вы действительно хотите вызывать зарегистрированные обработчики выхода, как кажется, вы делаете в этом случае, вы, вероятно, просто хотите использовать exit() вместо _Exit(), как предлагает этот ответ. - person John Bollinger; 09.02.2017
comment
@gperciva, обратите также внимание, что, хотя _exit() не является частью стандартной библиотеки C, стандартная библиотека имеет _Exit() начиная с C99, которая делает то же самое и немного более переносима. - person John Bollinger; 09.02.2017
comment
@gperciva, см. stackoverflow.com/questions/2329640/ для выхода() по сравнению с использованием _exit(). - person ReddyVeeru; 09.02.2017