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

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

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

int main(void) {
    int n = 123;
    double *data;

    data = (double *)malloc(n * sizeof(data));
    if(data[n-1] > 0)
        printf("Last element is positive!\n");
    return 0;
} 
Откомпилируем и запустим эту программу.

$ gcc test_for_valgrind.c
$ ./a.out
$

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

Ошибки условного перехода

Выполним теперь эту программу "под valgrind". Поскольку мы хотим, чтобы valgrind печатал информацию об ошибках со ссылками на строки исходного кода (информация о том, что в программе есть ошибки не очень полезна сама по себе), то сначала перекомпилируем программу с включением отладочной информации. (Вы можете самостоятельно проверить, что выводит valgrind, если программа откомплирована без ключа -g).

$ gcc -g test_for_valgrind.c
$ valgrind -q ./a.out
==12306== Conditional jump or move depends on uninitialised value(s)
==12306==    at 0x4005C1: main (test_for_valgrind.c:9)
==12306==
$

В нашем первом примере запустим valgrind с ключом -q, который минимизирует объем выводимой информации. Мы получили информацию об одной ошибке. На числа ==12306== можно не обращать никакого внимания &mdash это номер процесса, который был присвоен системой для нашей программы. Здесь сказано, что в строке 9 файла test_for_valgrind.c (в функции main) был выполнен условный переход (conditional jump), который зависит от неинициализированного значения или значений. Условный переход — это оператор if, оператор цикл while и т.п. В 9 строке нашего файла написано if(data[n-1] > 0). Источник ошибки ясен. Мы выделили память с помощью вызова malloc, но не записали туда никаких значений. Результат сравнения data[n-1] > 0 в этом случае может быть каким угодно.

Исправим эту ошибку, добавив цикл инициализации элементов массива числами 0, 1, 2, ...

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

int main(void) {
    int n = 123, k;
    double *data;

    data = (double *)malloc(n * sizeof(data));
    for(k = 0; k <= n; k++)
        data[k] = 1.0 * k;
    if(data[n-1] > 0)
        printf("Last element is positive!\n");
    return 0;
} 
Компилируем и запускаем исправленную программу и видим, что сообщение о положительности последнего элемента массива выводится: ю
$ gcc -g test_for_valgrind.c
$ ./a.out
Last element is positive!
$

Казалось бы, что теперь все нормально. Но, если мы запустим новую версию под valgrind, то мы опять получим сообщение об ошибке. Исправив одну ошибку мы допустили другую (очень распространенная ситуация).

Ошибки записи в память

Выполним измененную программу под valgrind и разберем выведенные сообщения.

$ gcc -g test_for_valgrind.c
$ valgrind -q ./a.out
==12378== Invalid write of size 8
==12378==    at 0x4005C3: main (test_for_valgrind.c:10)
==12378==  Address 0x51fc418 is 0 bytes after a block of size 984 alloc'd
==12378==    at 0x4C2AB80: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==12378==    by 0x40059C: main (test_for_valgrind.c:8)
==12378==
Last element is positive!
$

На этот раз ошибка связана с недопустимой операцией записи (Invalid write). Заметим, что несмотря на выполнения "недопусимой записи" наша программа все-таки выполнилась и напечата ожидаемый результат (Last element is positive!). Тем не менее в ней есть ошибка, которая может проявиться только при определенных условиях.

Первые две строчки сообщения об ошибке говорят, что ошибка записи произошла в файле test_for_valgrind.c в строке 10. Было записано 8 байт (это размер значения типа double). Третья строчка (Address 0x51fc418 is 0 bytes after a block of size 984 alloc'd) означает, что запись этих 8 байт проводилась по адресу, который идет сразу после выделенного блока памяти размером 984 байта. В этом числе угадывается размер нашего массива, так как 984=123*8. Последние две строчки говорят о том, где этот блок памяти был выделен (в функции malloc, которая была вызвана в функции main из 8-ой строки файла test_for_valgrind.c).

Причина этой ошибки в том, что управляющая переменная цикл инициализации значений массива for(k = 0; k <= n; k++) пробегает значения от 0 до n включительно, а последний допустимый индекс в массиве — это n-1.

Конкретно данная ошибка достаточно легко может быть найдена, а для ее исправления достаточно изменить нестрогое неравенство k <= n в условии выхода из цикла for на строгое k < n. К сожалению, не все ошибки столь просты. Но если Вам известн оператор, который приводит к ошибке (valgrind печатает имя файла и номер строки), то Вы можете использовать возможность отладчика GDB опеределять точку останова на заданной строке файла. Это позволит проанализировать значения переменных (при запуске программы в отладчике).

"Утечка" памяти

После исправления ошибки в цикле инициализации массива data наша программа выполняется без ошибок...

$ gcc -g test_for_valgrind.c
$ valgrind -q ./a.out
Last element is positive!
... но она все еще не идеальна. Если мы уберем ключ -q (который сокращает до минимума объем выводимой valgrind информации), то мы получим следующее сообщение.
$ valgrind ./a.out
==12561== Memcheck, a memory error detector
==12561== Copyright (C) 2002-2013, and GNU GPL'd, by Julian Seward et al.
==12561== Using Valgrind-3.10.1 and LibVEX; rerun with -h for copyright info
==12561== Command: ./a.out
==12561==
Last element is positive!
==12561==
==12561== HEAP SUMMARY:
==12561==     in use at exit: 984 bytes in 1 blocks
==12561==   total heap usage: 1 allocs, 0 frees, 984 bytes allocated
==12561==
==12561== LEAK SUMMARY:
==12561==    definitely lost: 984 bytes in 1 blocks
==12561==    indirectly lost: 0 bytes in 0 blocks
==12561==      possibly lost: 0 bytes in 0 blocks
==12561==    still reachable: 0 bytes in 0 blocks
==12561==         suppressed: 0 bytes in 0 blocks
==12561== Rerun with --leak-check=full to see details of leaked memory
==12561==
==12561== For counts of detected and suppressed errors, rerun with: -v
==12561== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
$

Строчка "in use at exit: 984 bytes in 1 blocks" в середине выдачи, и следующая за ней строчка "total heap usage: 1 allocs, 0 frees, 984 bytes allocated", показывают, что мы один раз вызвали функцию выделения памяти (в нашем случае malloc) и ни разу не вызвали функцию освобождения памяти free. Такая ситуация называется "утечкой памяти". Если запрошенная память больше не нужна в программе, то ее нужно освободить.

Как написано в последних строчках выдачи, valgrind предлагает запустить себя с указанием дополнительного ключа --leak-check=full. Если мы выполним эту просьбу (с добавлением ключа -q), то результат будет следующим.

$ valgrind --leak-check=full -q ./a.out
Last element is positive!
==12608== 984 bytes in 1 blocks are definitely lost in loss record 1 of 1
==12608==    at 0x4C2AB80: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==12608==    by 0x40059C: main (test_for_valgrind.c:8)
==12608==

В отличие от предыдущего запуска, когда мы только узнал, что осталась неосвобожденная память, здесь явно указано, что "пропало" (не было освобождо) 984 байта, которые были выделены одним блоком в строке 7 файла test_for_valgrind.c. Это очень полезная информация, так как обычно программа выделяет значительное число блоков памяти. Если мы не знаем, какая память осталась не освобожденной при окончании программы, то довольно сложно самостоятельно найти место, где она выделялась.

Идеальная программа

После исправления всех ошибок наша "идеальная" программа (конечно, она абсолютно бессмысленна по сути, так как мы сразу можем сказать, что последний жлемент массива всегда положительный) выглядит так:

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

int main(void) {
    int n = 123, k;
    double *data;

    data = (double *)malloc(n * sizeof(data));
    for(k = 0; k < n; k++)
        data[k] = 1.0 * k;
    if(data[n-1] > 0)
        printf("Last element is positive!\n");
    free(data);
    return 0;
} 
Запуск этой версии под valgrind не выводит никаких ошибок.
$ gcc -g test_for_valgrind.c
$ valgrind --leak-check=full -q ./a.out
Last element is positive!
$

Резюме

Как пользоваться valgrind: