Valgrind still reachable что значит
Перейти к содержимому

Valgrind still reachable что значит

  • автор:

Still Reachable Leak detected by Valgrind

All the functions mentioned in this block are library functions. How can I rectify this memory leak? It is listed under the «Still reachable» category. (There are 4 more, which are very similar, but of varying sizes)

 630 bytes in 1 blocks are still reachable in loss record 5 of 5 at 0x4004F1B: calloc (vg_replace_malloc.c:418) by 0x931CD2: _dl_new_object (dl-object.c:52) by 0x92DD36: _dl_map_object_from_fd (dl-load.c:972) by 0x92EFB6: _dl_map_object (dl-load.c:2251) by 0x939F1B: dl_open_worker (dl-open.c:255) by 0x935965: _dl_catch_error (dl-error.c:178) by 0x9399C5: _dl_open (dl-open.c:584) by 0xA64E31: do_dlopen (dl-libc.c:86) by 0x935965: _dl_catch_error (dl-error.c:178) by 0xA64FF4: __libc_dlopen_mode (dl-libc.c:47) by 0xAE6086: pthread_cancel_init (unwind-forcedunwind.c:53) by 0xAE61FC: _Unwind_ForcedUnwind (unwind-forcedunwind.c:126) 

Catch: Once I ran my program, it gave no memory leaks, but it had one additional line in the Valgrind output, which wasn’t present before:

Discarding syms at 0x5296fa0-0x52af438 in /lib/libgcc_s-4.4.4-20100630.so.1 due to munmap()

If the leak can’t be rectified, can someone atleast explain why the munmap() line causes Valgrind to report 0 «still reachable» leaks? Edit: Here’s a minimal test sample:

#include #include #include void *runner(void *param) < /* some operations . */ pthread_exit(NULL); >int n; int main(void) < int i; pthread_t *threadIdArray; n=10; /* for example */ threadIdArray = malloc((n+n-1)*sizeof(pthread_t)); for(i=0;i<(n+n-1);i++) < if( pthread_create(&threadIdArray[i],NULL,runner,NULL) != 0 ) < printf("Couldn't create thread %d\n",i); exit(1); >> for(i=0;i <(n+n-1);i++) < pthread_join(threadIdArray[i],NULL); >free(threadIdArray); return(0); > 
valgrind -v --leak-check=full --show-reachable=yes ./a.out 

asked Oct 1, 2010 at 15:22
user191776 user191776
Aug 23, 2018 at 17:40

5 Answers 5

There is more than one way to define «memory leak». In particular, there are two primary definitions of «memory leak» that are in common usage among programmers.

The first commonly used definition of «memory leak» is, «Memory was allocated and was not subsequently freed before the program terminated.» However, many programmers (rightly) argue that certain types of memory leaks that fit this definition don’t actually pose any sort of problem, and therefore should not be considered true «memory leaks».

An arguably stricter (and more useful) definition of «memory leak» is, «Memory was allocated and cannot be subsequently freed because the program no longer has any pointers to the allocated memory block.» In other words, you cannot free memory that you no longer have any pointers to. Such memory is therefore a «memory leak». Valgrind uses this stricter definition of the term «memory leak». This is the type of leak which can potentially cause significant heap depletion, especially for long lived processes.

The «still reachable» category within Valgrind’s leak report refers to allocations that fit only the first definition of «memory leak». These blocks were not freed, but they could have been freed (if the programmer had wanted to) because the program still was keeping track of pointers to those memory blocks.

In general, there is no need to worry about «still reachable» blocks. They don’t pose the sort of problem that true memory leaks can cause. For instance, there is normally no potential for heap exhaustion from «still reachable» blocks. This is because these blocks are usually one-time allocations, references to which are kept throughout the duration of the process’s lifetime. While you could go through and ensure that your program frees all allocated memory, there is usually no practical benefit from doing so since the operating system will reclaim all of the process’s memory after the process terminates, anyway. Contrast this with true memory leaks which, if left unfixed, could cause a process to run out of memory if left running long enough, or will simply cause a process to consume far more memory than is necessary.

Probably the only time it is useful to ensure that all allocations have matching «frees» is if your leak detection tools cannot tell which blocks are «still reachable» (but Valgrind can do this) or if your operating system doesn’t reclaim all of a terminating process’s memory (all platforms which Valgrind has been ported to do this).

Valgrind Memcheck: Different ways to lose your memory

Valgrind Memcheck: Different ways to lose your memory

Valgrind is an instrumentation framework for building dynamic analysis tools that check C and C++ programs for errors. Memcheck is the default tool Valgrind uses when you don’t ask it for another tool. Other useful tools you can select (using valgrind tool=toolname ) are:

  • cachegrind and callgrind , to do cache and call-graph function profiling
  • helgrind and drd , to do thread error and data-race detection
  • massif and dhat , to do dynamic heap usage analysis

Each of these tools deserves an article of its own, but here we will concentrate on Memcheck.

Detecting memory leaks with Valgrind Memcheck

Memcheck tracks all memory reads, writes, allocations, and deallocations in a C or C++ program. The tool can detect many different memory errors. For instance, it detects reads or writes before or after allocated memory blocks. It warns about the use of (partially) undefined values in conditional code or passing such values to system calls. It will also notify you about bad or double deallocation of memory blocks. But for now, we will discuss memory leak detection with Memcheck.

Generating a leak summary

When you run Valgrind on your program without any additional arguments, it produces a summary of the different kinds of leaks it has detected. For example, valgrind ./myprog might produce the following summary:

LEAK SUMMARY: definitely lost: 48 bytes in 1 blocks indirectly lost: 24 bytes in 3 blocks possibly lost: 0 bytes in 0 blocks still reachable: 14 bytes in 1 blocks suppressed: 0 bytes in 0 blocks

Memcheck reports leaks in five categories: definitely lost, indirectly lost, possibly lost, still reachable, and suppressed. The first four categories indicate different kinds of memory blocks that weren’t freed before the program ended. If you aren’t interested in specific blocks, you can tell Valgrind not to report them (you’ll see how shortly). The summary also shows you the number of bytes lost and how many blocks they are in, which tells you whether you are losing lots of small allocations, or a few large ones.

The following sections explain each category.

Definitely lost

The first category, definitely lost, is generally the most urgent kind of leak to track down, because there is no way to use or recover that memory. Let’s look at an example of a small program that simply calls output_report a couple of times. That function prints a small banner and a number each time. As we will see, the memory we reserve for the report banner will be definitely lost (multiple times) when the program finishes:

#include #include #include char * create_banner () < const char *user = getenv ("USER"); size_t len = 1 + 2 * 4 + strlen (user) + 1; char *b = malloc (len); sprintf (b, "\t|** %s **|", user); return b; >void output_report (int nr) < char *banner = create_banner (); puts (banner); printf ("Number: %d\n", nr); printf ("\n"); >int main ()

Compile this code with gcc -Wall -g -o definitely definitely.c and run it under Valgrind, asking for details with valgrind —leak-check=full ./definitely . Now, before the leak summary, Valgrind will show backtraces where the program allocated memory that was ultimately lost:

42 bytes in 3 blocks are definitely lost in loss record 1 of 1 at 0x4C29F33: malloc (vg_replace_malloc.c:309) by 0x4011C7: create_banner (definitely.c:10) by 0x401200: output_report (definitely.c:18) by 0x40124C: main (definitely.c:28)

Note that Memcheck found three leaks, which it reports as one loss record because they have identical backtraces. By default, it requires the whole backtrace to be the same in order to consider leaks similar enough to report together. If you want Memcheck to combine more leaks, you can use —leak-resolution=low or —leak-resolution=med to group leaks that have only two or four backtrace entries in common. This is useful if Memcheck reports lots of leaks with slightly different backtraces that you suspect are probably the same issue. You can then concentrate on the record with the highest number of bytes (or blocks) lost.

Still reachable

In the previous example, it is clear we should free banner after use. We could do that at the end of the output_report function by adding free (banner) . Then, when running under Valgrind again, it will happily say All heap blocks were freed — no leaks are possible .

But we are clever and see that the code reuses the same banner for each report. So, we define banner as a static top-level variable in our code and move the create_banner call to the main function, so that create_banner is called only once:

char *banner; void output_report (int nr) < puts (banner); printf ("Number: %d\n", nr); printf ("\n"); >int main

Note how we again forget to call free , this time at the end of main . Now, when running under Valgrind, Memcheck will report still reachable: 14 bytes in 1 blocks and zero bytes lost in any other category.

But the output offers no details of loss records with backtraces for memory blocks that are still reachable, even though we ran with —leak-check=full . This is because Memcheck thinks the error is not very serious. The memory is still reachable, so the program could still be using it. In theory, you could free it at the end of the program, but all memory is freed at the end of the program anyway.

Although still reachable memory isn’t a real issue in theory, you might still want to look into it. You might want to see whether you could free a given block earlier, which might lower memory usage for longer running programs. Or because you really like to see that statement All heap blocks were freed — no leaks are possible . To get the details you need, add —show-leak-kinds=reachable or —show-leak-kinds=all to the Valgrind command line (together with —leak-check=full ). Now you will also get backtraces showing where still reachable memory blocks were allocated in your program.

Possibly lost

To explore the other categories of leaks, we change our program a little to include some lists of numbers to report. Each report will have a different list of numbers to report. The complete data structure is allocated at the start of the program. And for each set of numbers, we allocate a new block of numbers. To keep things simple (too simple, as Memcheck will point out) we keep just one pointer to the current numbers struct to be printed. Although we create three sets of numbers, we output only two reports:

#include #include struct numbers < int n; int *nums; >; int n; struct numbers *numbers; void create_numbers (struct numbers **nrs, int *n) < *n = 3; *nrs = malloc ((sizeof (struct numbers) * 3)); struct numbers *nm = *nrs; for (int i = 0; i < 3; i++) < nm->n = i + 1; nm->nums = malloc (sizeof (int) * (i + 1)); for (int j = 0; j < i + 1; j++) nm->nums[j] = i + j; nm++; > > void output_report () < puts ("numbers"); for (int i = 0; i < numbers->n; i++) printf ("Number: %d\n", numbers->nums[i]); printf ("\n"); > int main () < create_numbers (&numbers, &n); for (int i = 0; i < 2; i++) < output_report (); numbers++; >return 0; >

When we compile this program with gcc -Wall -g -o possibly possibly.c and then run it under Valgrind with valgrind —leak-check=full ./possibly , Valgrind reports possibly lost: 72 bytes in 4 blocks . And because we ran with —leak-check=full , it also reports the backtraces:

24 bytes in 3 blocks are possibly lost in loss record 1 of 2 at 0x4C29F33: malloc (vg_replace_malloc.c:309) by 0x4011C3: create_numbers (possibly.c:22) by 0x40128F: main (possibly.c:41) 48 bytes in 1 blocks are possibly lost in loss record 2 of 2 at 0x4C29F33: malloc (vg_replace_malloc.c:309) by 0x401185: create_numbers (possibly.c:17) by 0x40128F: main (possibly.c:41)

Memcheck calls this memory possibly lost because it can still see how to access the blocks of memory. The numbers pointer points to the third block of numbers. If we kept some extra information, we could theoretically count backward to the beginning of this block of memory and access the rest of the information, or deallocate the whole block and the other memory it points to.

But Memcheck thinks this is most likely a mistake. And in our example, as in most such cases, Memcheck is right. When walking a data structure without keeping a reference to the structure itself, we can never reuse or free the structure. We should have used the numbers pointer as a base and used an (array) index to pass the current record as output_report (&numbers[i]) . Then, Memcheck would have reported the data blocks as still reachable. (There is still a memory leak, but not a severe one, because there is a direct pointer to the memory and it could easily be freed.)

Indirectly lost

In the previous example Memcheck reported a possibly lost block because the numbers pointer was still pointing inside an allocated block. We might be tempted to fix it by simply clearing the pointer after the output_report calls by doing numbers = NULL; to indicate that there is no current numbers list to report. But then we have also just lost the last pointer to our memory data blocks. We should have freed the memory first, but we can’t do it now because we don’t have a pointer to the start of the data structure anymore:

int main () < create_numbers (&numbers, &n); for (int i = 0; i < 2; i++) < output_report (); numbers++; >numbers = NULL; return 0; >

Now Memcheck will report the memory as definitely lost. And because the memory block contained pointers to other memory blocks, those blocks are reported as indirectly lost. If we run with —leak-check=full we see a backtrace for the main numbers memory block:

72 (48 direct, 24 indirect) bytes in 1 blocks are definitely lost in loss record 2 of 2 at 0x4C29F33: malloc (vg_replace_malloc.c:309) by 0x401185: create_numbers (possibly.c:17) by 0x40128F: main (possibly.c:41) LEAK SUMMARY: definitely lost: 48 bytes in 1 blocks indirectly lost: 24 bytes in 3 blocks possibly lost: 0 bytes in 0 blocks still reachable: 0 bytes in 0 blocks suppressed: 0 bytes in 0 blocks

Note how there are no backtraces for the indirectly lost blocks. This is because Memcheck believes you will probably fix that when you fix the definitely lost block. If you do free the definitely lost block, but not the blocks of memory that were indirectly pointed to, next time you run your partially fixed program under Valgrind, Memcheck will report those indirectly lost blocks as definitely lost (and now with a backtrace). So by iteratively fixing the definitely lost memory leaks, you will eventually fix all indirectly lost memory leaks.

If you cannot immediately find the definitely lost block that caused some indirectly lost blocks, it might be informative to see the backtraces for where the indirectly lost blocks were created. When using —leak-check=full you can do that by adding —show-leak-kinds=reachable or —show-leak-kinds=all to the valgrind command line.

Suppressed

By default, Memcheck counts definitely lost and possibly lost blocks as errors with —leak-check=full . It will also show where those blocks were allocated. It doesn’t regard indirectly lost blocks or still reachable lost blocks as errors by default. And it won’t show backtraces for where those still reachable or indirectly lost blocks were allocated, unless explicitly asked to do so with —show-leak-kinds=all .

Indirectly lost blocks will disappear (or turn into definitely lost blocks) when you resolve the definitely lost issues. Without definitely lost blocks, there can be no indirectly lost blocks. For reachable blocks, it might still make sense to see whether you can deallocate them early, in order to lower memory usage of your program. Or explicitly free them at the end of your program to make sure all memory is really accounted for and cleaned up.

But there might be reasons for not fixing all memory leaks. They might occur in a library you are using that cannot easily be replaced. Or you might be convinced that a possibly lost block isn’t really an error. If, in the original definitely lost example, you decide not to fix the issue and to keep the memory leak, you might want to generate a suppression so Memcheck won’t complain about this particular block again. You can do this easily by running valgrind —leak-check=full —gen-suppressions=all ./definitely which generates an example suppression:

< insert_a_suppression_name_here Memcheck:Leak match-leak-kinds: definite fun:malloc fun:create_banner fun:output_report fun:main >

You can put that into a file (say, local.supp ), replacing insert_a_suppression_name_here with something descriptive such as small leak in create_banner . Now, when you run valgrind —suppressions=./local.supp —leak-check=full ./definitely , the leak will be suppressed:

LEAK SUMMARY: definitely lost: 0 bytes in 0 blocks indirectly lost: 0 bytes in 0 blocks possibly lost: 0 bytes in 0 blocks still reachable: 0 bytes in 0 blocks suppressed: 42 bytes in 3 blocks

There won’t be any more output for any of the suppressed blocks. But if you want to see which suppressions were used, you can add —show-error-list=yes (or -s ) to the valgrind command line. That option makes Valgrind show the suppression name, suppression file, line number, and how many bytes and blocks were suppressed by that suppression rule:

used_suppression: 1 small leak in create_banner ./local.supp:2 suppressed: 42 bytes in 3 blocks

Test suite integration

When you have resolved all memory leak issues, or when you have suppressions for those you don’t care about, you might want to integrate Valgrind into your test suite to catch any new memory leaks early. If you use —error-exitcode= , Valgrind will change the program’s exit code to the given number when an error (memory leak) is detected. You can also use —quiet (or -q ) to make Valgrind silent, so that it doesn’t interfere with the normal stdout and stderr of the program, except for error output, so that you can compare the program output as usual.

Remember that by default Memcheck regards only definitely lost and possibly lost memory blocks as errors. You can change that by using —errors-for-leak-kinds=set . If you are interested in getting an error only for definitely lost blocks, you can use —errors-for-leak-kinds=definite . When your test programs always free all memory blocks, including still reachable blocks, you can use —errors-for-leak-kinds=definite,possibly,reachable or —errors-for-leak-kinds=all . Note that —errors-for-leak-kinds=set , which works together with —error-exitcode=number and the above mentioned —show-leak-kinds=set option, which determines which backtraces to show, are independent. But in general you will want them to be the same, so that you will always get a backtrace for a memory error.

So, a good way to run your tests is valgrind -q —error-exitcode=99 —leak-check=full ./testprog . If you have any local suppressions, you can add —suppressions=local.supp . And if you really want all your test cases to be totally free from any kind of memory leak, add —show-leak-kinds=all —errors-for-leak-kinds=all .

Last updated: April 22, 2021

memcheck: the debugger

valgrind — это подпроект KDE, который направлен на отлов багов в С и C++ программах.

Лирики и истории тут не будет, но можно почитать ссылку.

Краткая теория как работает valgrind:

valgrind — это набор утилит, построенных поверх либы-эмулятора процессора libvex (причем libvex может эмулировать не только тот процессор, на котором она рабоотает, но и другие). То есть libvex читает инструкции процессора и эмулирует их, отслеживая что какая инструкция куда переместила (с точность до бита!).

В состав valgrind входит утилита memcheck (самая популярная утилита, работает по умолчанию). В послании расскажу пока только про нее.

  1. обращение за пределы выделенной памяти вроде такого:
 int main()  char * p = malloc (1); p[1] = '!'; // пишем сразу за границу буфера free (p); return 0; >
  1. использование неинициализированных данных
 #include int main()  int trash; // не инициализируем printf ("%d\n", trash); return 0; >
 int main()  char * p = malloc (1); // и не освобождаем return 0; >

Чтобы получить вменяемую иформацию о том, где произошла ошибка — нужно:

  • собрать программу с отладкой. Для этого у gcc есть семейство ключей, начинающихся на -g.
  • (не обязательно) выставить уровень оптимизаций поменьше. Снижение уровня оптимизаций улучшает точность отладочной информации: функции и шаблоны не инлайнятся, порядок инструкций не меняется и т.д. Так что изменяя оптимизации вы трейсите немного разные программы (в которых могут встречаться совсем разные баги). Но обычно это не проблема.

Собираем, запускаем (я взял пример 2. выше):

 gcc -ggdb -O0 main.c -o main valgrind --track-origins=yes ./main
==2396== Use of uninitialised value of size 8 ==2396== at 0x4E6D81B: _itoa_word (in /lib64/libc-2.11.2.so) ==2396== by 0x4E6EA7C: vfprintf (in /lib64/libc-2.11.2.so) ==2396== by 0x4E788F9: printf (in /lib64/libc-2.11.2.so) ==2396== by 0x400552: main (main.c:4) ==2396== Uninitialised value was created by a stack allocation ==2396== at 0x400534: main (main.c:2)

Из этого всего мы видим, что в строке main.c:4 произошло использование неинициализированной переменной размера 8. Создано значение на стеке в строке main.c:2. Созданное значение отслеживается только со включенным ключом –track-origins=yes. Ключ появился в valgrind в версии 3.5.0.

Всё просто. Мы видим стек вызовов. В самом верху — самые близкие к ошибке строки, ниже — те, кто эти строки вызвал (размотан стек).

По умолчанию valgrind трейсит только один процесс и пишет вывод на stdout. Это классно для консольных интерактивных программ, но неудобно при отладке демонов. На этот случай у valgrind есть вагон ключей:

  • — -trace-children=yes — трейсит все содраваемые процессом подпроцессы. Для этого в логе и пишется PID процесса вначале: ==PID==.
  • — -log-file= — выводит лог в отдельный файл.

Есть и другие интересные полезные ключи (valgrind –help):

  • — -num-callers=NUM — глубина размотки стека при отображении ошибки (valgrind отслеживает какая функция какую вызвала). В примере выше у нас 4 уровня вложенности: **_itoa_word, vfprintf, printf, main**. По умолчанию отслеживается 12 вызовов. Часто это бывает мало (у нас в проекте до 30).
  • — -verbose — показывает много всего интересного (и не очень): какие функции перехватываются valgrind и прочая ерунда.
  • — -track-fds=yes — отслеживает незакрытые файлы при завершении программы (особо больной вопрос для демонов).
  • — -db-attach=yes — присосаться к программе отладчиком в точке возникновения ошибки (в случае с демонами это не так просто :]).
  • — -leak-check=full — ищет утечки памяти
  • — -show-reachable=yes — показывает неосвобожденную, но неутекшую (есть ссылки из программы) память при завершении программы.
  • — -malloc-fill=val / — -free-fill=val — забавает памят после malloc/free значениями val. По умолчанию 0 (не всегодя хорошо, так как гореспрограммисты любят совать проверки на 0 без повода).

У C++ программ (особенно написаных с использованием STL) есть одна засада, усложняющая ловить ошибки: многие функции работы с памятью выделяют боьлше памяти, чем это реально надо. Эта оптимизация скрывает ошибки обращения за границы выделенной памяти в контейнерах типа std::vector.

К счастью в шаблонах контейнеров gcc есть волшебная переменная среды, отключающая такие оптимизации: GLIBCXX_FORCE_NEW.

Итак, наша коммандная строка параноика довольно жирная. Слепим микроскрипт:

 #!/bin/sh # лежит где-то в ~/bin/vg.sh GLIBCXX_FORCE_NEW=1 \ valgrind \ --track-origins=yes \ --trace-children=yes \ --num-callers=50 \ --track-fds=yes \ --leak-check=full \ --show-reachable=yes \ --malloc-fill=0xa1 \ --free-fill=0xa1 \ "$@"
 vg.sh --log-file=valgrind.log ./my-nice-program --my-nice-params

Работает даже на демонах :]

Еще читаете? Офигеть! Для самых терпеливых: черная магия!

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

С их помощью можно решать самые разные задачи.

Например, представим, что у нас в программе есть свой распределитель памяти и мы хотим отслеживать утечки в нем:

 #include typedef unsigned char u8; static u8 * mempool = 0; static u8 * mempool_p = 0; static size_t mempool_size = 0; void mempool_init (size_t pool_size)  mempool_p = mempool = (u8*)malloc (pool_size); if (mempool) mempool_size = pool_size; > void mempool_destroy (void)  free (mempool); > void * mempool_alloc (size_t obj_size)  // не влезет if (mempool_p + obj_size > mempool + mempool_size) return 0; void * result = mempool_p; mempool_p += obj_size; return result; > void mempool_free (void * obj, size_t obj_size)  // освобождаем только последний. такая вот халтура :] if (mempool_p == (u8*)obj + obj_size) mempool_p -= obj_size; > int main ()  mempool_init (1000); void * p1 = mempool_alloc (10); void * p2 = mempool_alloc (20); mempool_free (p2, 20); // Забыли //mempool_free (p1, 10); mempool_destroy (); return 0; >
$ g++ -ggdb -O0 a.c -o a $ vg.sh ./a . ==24548== HEAP SUMMARY: ==24548== in use at exit: 0 bytes in 0 blocks ==24548== total heap usage: 1 allocs, 1 frees, 1,000 bytes allocated

Врёт! p1 то не освобожден! У valgrind есть чудомакросы, помечающие память, как выделенную из аллокатора:

 VALGRIND_MALLOCLIKE_BLOCK(addr, sizeB, rzB, is_zeroed) VALGRIND_FREELIKE_BLOCK(addr, rzB)

Их больше, но заюзаем только эти. Изменим исходнег:

 #include #include  // 1 typedef unsigned char u8; static u8 * mempool = 0; static u8 * mempool_p = 0; static size_t mempool_size = 0; void mempool_init (size_t pool_size)  mempool_p = mempool = (u8*)malloc (pool_size); if (mempool) mempool_size = pool_size; > void mempool_destroy (void)  free (mempool); > void * mempool_alloc (size_t obj_size)  // не влезет if (mempool_p + obj_size > mempool + mempool_size) return 0; void * result = mempool_p; mempool_p += obj_size; VALGRIND_MALLOCLIKE_BLOCK(result, obj_size, 0, 0); // 2 return result; > void mempool_free (void * obj, size_t obj_size)  // освобождаем только последний. такая вот халтура :] if (mempool_p == (u8*)obj + obj_size) mempool_p -= obj_size; VALGRIND_FREELIKE_BLOCK(obj, 0); // 3 > int main ()  mempool_init (1000); void * p1 = mempool_alloc (10); void * p2 = mempool_alloc (20); mempool_free (p2, 20); // Забыли //mempool_free (p1, 10); mempool_destroy (); return 0; >
$ g++ -ggdb -O0 a.c -W -Wall -o a a.c: In function ‘int main()’: a.c:35: предупреждение: неиспользуемая переменная ‘p1’ # хехе, нашу переменную засекли :] $ vg.sh ./a . ==8958== HEAP SUMMARY: ==8958== in use at exit: 1,000 bytes in 1 blocks ==8958== total heap usage: 3 allocs, 2 frees, 1,030 bytes allocated ==8958== ==8958== 1,000 bytes in 1 blocks are still reachable in loss record 1 of 1 ==8958== at 0x4C2635E: malloc (vg_replace_malloc.c:236) ==8958== by 0x4008E5: mempool_init(unsigned long) (a.c:11) ==8958== by 0x400A8C: main (a.c:33)

Оп! Мы немножко запутали valgrind тем, что у нас p1 и mempool — один адрес. Из-за этого он неправильно отределяет точку выделения памяти. Это баг в valgrind! :] Ну не важно. Главное, что псевдоутечка детектируется.

Второй пример — поиск врага, модифицирующего память.

 #include #include #include void init (char * p); void process (const char * p); int main ()  char * p = (char *)malloc (100); init (p); printf ("before process: p[33] = %c\n", p[33]); process (p); printf ("after process: p[33] = %c\n", p[33]); free (p); return 0; > // Делаем вид, что дальше идёт много чужого неясного кода void init (char * p)  memset (p, '+', 100); > void process (const char * p)  char * q = (char *)p; // ужас! сейчас испоганит! q[33] = '-'; >

Глядя на прототип функции process никак не скажешь, что она может изменить данные p[33], но тест printf говорит об обратном.

Как найти врага? Сделаем память “недоступной” и проверим:

 #include #include #include #include  void init (char * p); void process (const char * p); int main ()  char * p = (char *)malloc (100); init (p); printf ("before process: p[33] = %c\n", p[33]); VALGRIND_MAKE_MEM_NOACCESS(p, 100); process (p); printf ("after process: p[33] = %c\n", p[33]); free (p); return 0; > // Делаем вид, что дальше идёт много чужого неясного кода void init (char * p)  memset (p, '+', 100); > void process (const char * p)  char * q = (char *)p; // ужас! сейчас испоганит! q[33] = '-'; >
$ g++ -ggdb -O0 a.c -W -Wall -o a $ vg.sh ./a . before process: p[33] = + ==22521== Invalid write of size 1 ==22521== at 0x400A79: process(char const*) (a.c:31) ==22521== by 0x400A0A: main (a.c:16) ==22521== Address 0x5931061 is 33 bytes inside a block of size 100 alloc'd ==22521== at 0x4C2635E: malloc (vg_replace_malloc.c:236) ==22521== by 0x40096F: main (a.c:12) ==22521== ==22521== Invalid read of size 1 ==22521== at 0x400A13: main (a.c:17) ==22521== Address 0x5931061 is 33 bytes inside a block of size 100 alloc'd ==22521== at 0x4C2635E: malloc (vg_replace_malloc.c:236) ==22521== by 0x40096F: main (a.c:12) ==22521== after process: p[33] = - . 

read нас не очень интересует, а вот write — это и есть наш запрятанный глюк: (a.c:31)

[SOLVED]valgrind: still reachable

Вопрос: это linux «лениво» освобождает память, или программист я чего-то не освободил?

Ответ: память теоретически может быть освобождена, но не освобождена. Т.е. программа не освобождает память, а оставляет это ос.

n0name_anonymous
13.10.20 05:31:38 MSK

Нет кода – нет ответа.

Siborgium ★★★★★
( 13.10.20 05:58:30 MSK )

«still reachable» means your program is probably ok – it didn’t free some memory it could have. This is quite common and often reasonable. Don’t use —show-reachable=yes if you don’t want to see these reports.

hobbit ★★★★★
( 13.10.20 08:36:01 MSK )

Rerun with —leak-check=full to see details of leaked memory

anonymous
( 13.10.20 09:42:51 MSK )
Ответ на: комментарий от hobbit 13.10.20 08:36:01 MSK

it didn’t free some memory it could have

Программа не освободила память, которую могла иметь? Не понимаю.

Т.е. память была выделена, но не была освобождена? Т.е. проблема в коде, и какая-то память выделяется, но не освобождается?

n0name_anonymous
( 13.10.20 12:07:30 MSK ) автор топика
Ответ на: комментарий от Siborgium 13.10.20 05:58:30 MSK

Сформулирую иначе: возможно ли, что каждая выделенная в куче память освобождается в коде программы, но still reachable не нуль?

n0name_anonymous
( 13.10.20 12:13:17 MSK ) автор топика
Ответ на: комментарий от n0name_anonymous 13.10.20 12:13:17 MSK

Если выделение памяти произошло в библиотеке, то возможно.

xaizek ★★★★★
( 13.10.20 13:59:41 MSK )
Ответ на: комментарий от xaizek 13.10.20 13:59:41 MSK

Сформулирую иначе: возможно ли, что каждая выделенная в куче память освобождается в коде программы, но still reachable не нуль?

Если выделение памяти произошло в библиотеке, то возможно.

Ясно. Возможно ли, что какая-то выделенная в коде программы память не освобождается в коде программы, но:

==1951== LEAK SUMMARY: ==1951== definitely lost: 0 bytes in 0 blocks ==1951== indirectly lost: 0 bytes in 0 blocks ==1951== possibly lost: 0 bytes in 0 blocks ==1951== still reachable: 472 bytes in 1 blocks ==1951== suppressed: 0 bytes in 0 blocks 

n0name_anonymous
( 13.10.20 14:44:49 MSK ) автор топика
Ответ на: комментарий от n0name_anonymous 13.10.20 14:44:49 MSK

Тоже может быть. Где-то остаётся указатель достижимый из глобальной памяти или статической переменной. —leak-check=full —show-reachable=yes должно показать, о какой именно памяти идёт речь.

xaizek ★★★★★
( 13.10.20 14:54:52 MSK )

still reachable означает что указатель на память не утерян, память может быть теоритически освобождена, но она не освобождена.

#include void* a; int main()
valgrind ./a.out ==16023== Memcheck, a memory error detector ==16023== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al. ==16023== Using Valgrind-3.16.1 and LibVEX; rerun with -h for copyright info ==16023== Command: ./a.out ==16023== ==16023== ==16023== HEAP SUMMARY: ==16023== in use at exit: 472 bytes in 1 blocks ==16023== total heap usage: 1 allocs, 0 frees, 472 bytes allocated ==16023== ==16023== LEAK SUMMARY: ==16023== definitely lost: 0 bytes in 0 blocks ==16023== indirectly lost: 0 bytes in 0 blocks ==16023== possibly lost: 0 bytes in 0 blocks ==16023== still reachable: 472 bytes in 1 blocks ==16023== suppressed: 0 bytes in 0 blocks ==16023== Rerun with --leak-check=full to see details of leaked memory ==16023== ==16023== For lists of detected and suppressed errors, rerun with: -s ==16023== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0) 

P.S. это «типа норм» считается, например, PVS-Studio говорили что они только выделяют память, а освобождает сама система, после того как программа завершит анализ файла. Ещё Уолтер Брайт говорил, что в его dmd dlang compiler память только выделяется, а освободится системой, и это одна из фич, которая позволяет компилятору D быстрее собирать код, чем другим компиляторам…

fsb4000 ★★★★★
( 13.10.20 15:08:09 MSK )
Последнее исправление: fsb4000 13.10.20 15:12:03 MSK (всего исправлений: 1)

Ответ на: комментарий от fsb4000 13.10.20 15:08:09 MSK

still reachable означает что указатель на память не утерян, память может быть теоритически освобождена, но она не освобождена.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *