Valgrind is not a leak checking tool

Overview:

In my community, Valgrind is the most misunderstood tool I know of. Valgrind is more than just a memory leak checker. It just contains a tool to check for memory leaks. But what I'm trying to say is that this tool is precisely the least useful component of Valgrind.

Without changing the way Valgrind is called, you can get far more valuable information than most people think. Valgrind finds potential bugs before your program crashes; it not only tells you where the bug is, but also why (in English). Valgrind is first a tool for detecting unknown behavior, and second it's a function and memory An analysis tool, then a data race condition detection tool, and finally a memory leak checker.

First and foremost:

To run Valgrind, you just need to change to the directory where your program is located and run the following command:

valgrind ./myProgram myProgramsFirstArg myProgramsSecondArg

No special parameters are required.

You will see both the output of your program, and the debug output generated by Valgrind (those lines starting with '=='). If your program is compiled with the -g option (to generate debug symbol information), Valgrind will provide more helpful information (such as the line number of the executed code).

For the purpose of this article, please ignore all Valgrind output after the "HEAD SUMMARY"line . That's exactly the part that this article doesn't care about: the content leak summary.

What can it detect?

1) Misuse of uninitialized values. This is also its basic function:

bool condition;
if (condition) {
  //Do the thing
}

The funny thing is that most of the time your program just keeps running, and then when it gets there, it fails without warning. It may (most of the time) appear to work the way you expect it to. In theory, if your program has errors, it should error every time you run it. These mistakes are hard and fast to show. We can only fix it by identifying where the bug is. The problem is that we don't assign any value to that boolean variable from the start, and it's not automatically initialized by the program. At this point, its value could be any random value that happens to be left in its memory location.

In the above example Valgrind would output a line like this:

==2364== Conditional jump or move depends on uninitialized value(s)
==2364==    at 0x400916: main (test.cpp:106)

Note: The above output gives the reason why the code is causing the unknown behavior, not just the location. Even better, Valgrind catches these unknown behaviors before they cause the program to crash.

Obvious miscalculations like the one above are hard to come by, but the following one is not so easy to spot:

bool condition;
if (foo) {
  condition = true;
}
if (bar) {
  condition = false;
}
if (baz) {
  condition = true;
}
if (condition) {
  //Do the thing
}

Here we successfully initialize the condition only some of the time, but not all. Valgrind can still detect these undetermined behaviors.

This error can be avoided at the source by using some defensive programming techniques. I prefer to give each variable an initial value. Or use the auto keyword to force you to initialize a variable (you can't infer the type of that variable without a value). You can check out Herb Sutter's blog for more on the auto keyword.

2) Manipulate memory that you shouldn't touch. Read and write memory that has never been allocated, memory that has been released; access memory beyond the boundaries of an allocated memory; memory that cannot be read or written on the stack.

one example:

  vector<int> v { 1, 2, 3, 4, 5 };
  v[5] = 0; //Oops

Did you see it?

If I run this program on my computer, there is probably nothing wrong. It may run more than 20 times without dying once, but it is definitely wrong. Even if I happen to be using GDB (another debugging tool) to debug it and it hangs, I can get at most a stack call log, but that makes it not the cause of the problem, but the manifestation of the problem form.

Here is Valgrind's output for the above problem:

==2710== Invalid write of size 4
==2710==    at 0x400961: foo() (test.cpp:85)
==2710==    by 0x4009A2: main (test.cpp:89)
==2710==  Address 0x5a1d054 is 0 bytes after a block of size 20 alloc'd
==2710==    at 0x4C2B0E0: operator new(unsigned long) (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==2710==    by 0x400EDF: __gnu_cxx::new_allocator<int>::allocate(unsigned long, void const*) (new_allocator.h:104)
==2710==    by 0x400DCE: std::_Vector_base<int, std::allocator<int> >::_M_allocate(unsigned long) (in /home/mark/test/a.out)
==2710==    by 0x400C5F: void std::vector<int, std::allocator<int> >::_M_range_initialize<int const*>(int const*, int const*, std::forward_iterator_tag) (stl_vector.h:1201)
==2710==    by 0x400AF4: std::vector<int, std::allocator<int> >::vector(std::initializer_list<int>, std::allocator<int> const&) (stl_vector.h:368)
==2710==    by 0x400943: foo() (test.cpp:84)
==2710==    by 0x4009A2: main (test.cpp:89)

If you are not familiar with STL's stack calls, the above things are not easy to understand. Let's take a closer look.


The first line tells you why your code has undetermined behavior. There is an "Invalid write of size 4" here. size 4 means I wrote something 4 bytes big. On my machine this is probably an int type. invalid means I touched memory that I shouldn't touch. This was a near miss: I wrote past the end of my vector.

Now let's look at lines 2,3. This is the stack call information that Valgrind thinks you are most interested in. Indeed, in this example, the code in question is in foo, and main is the function that calls foo.

The fourth line describes the "you used memory out of bounds" problem in more detail. ,

The rest is more detailed stack call information including STL. In fact, the problem never occurs in STL. (Well, almost never.)

3) Misuse std::memcpyand other functions built on top of this function can cause your source and destination array addresses to overlap (please read my article first explaining why std::memcpy is deprecated, and keep in mind when you use Other seemingly good higher level abstraction layers, you still can't avoid indirect calls to std::memcpy)

I won't give example code for this and the next item here; I think in modern code this is uncommon, and if you're unlucky enough to run into this kind of problem, simply run the Valgrind command without any arguments, it Both types of issues can be reported to you.

4) Invalid memory deallocation (almost none in modern code, you should prefer smart pointers anyway)

5) Data race:

If I run a command like:

valgrind --tool=helgrind ./myProgram

where myProgram contains the following code:

  auto x = 0;
  thread([&] {
    ++x;
  }).detach();
  ++x;

I will get Valgrind feedback like this:

==2872== Possible data race during read of size 4 at 0xFFEFFFE8C by thread #1
==2872== Locks held: none
==2872==    at 0x401081: main (test.cpp:96)
==2872== 
==2872== This conflicts with a previous write of size 4 by thread #2
==2872== Locks held: none
==2872==    at 0x40103A: main::{lambda()#1}::operator()() const (test.cpp:94)
==2872==    by 0x401F2D: void std::_Bind_simple<main::{lambda()#1} ()>::_M_invoke
<>(std::_Index_tuple<>) (functional:1732)
==2872==    by 0x401E84: std::_Bind_simple<main::{lambda()#1} ()>::operator()() 
(functional:1720)
==2872==    by 0x401E1D: std::thread::_Impl<std::_Bind_simple<main::{lambda()#1} 
()> >::_M_run() (thread:115)
==2872==    by 0x4EEEBEF: ??? (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.19)
==2872==    by 0x4C30E26: ??? (in /usr/lib/valgrind/vgpreload_helgrind-amd64-linu
x.so)
==2872==    by 0x535F181: start_thread (pthread_create.c:312)
==2872==    by 0x566FEFC: clone (clone.S:111)

It tells me that my data is not properly protected. Without synchronization via mutex, I share data.

I have to say that although it detects errors in the code, it still contains a bunch of inaccurate diagnostics in its output (here it prints a bunch of std::shared_ptr being called internally by std::thread etc. redundant information). It looks like Valgrind still needs to work harder on information filtering. Of course you can also write a simple D script or Python script to help helgrind filter out useful information.

6) Oye... Memory leaked, you don't have smart pointers enabled yet.

run:

valgrind --leak-check=full ./myProgram

(If you forget which parameter it is, just run Valgrind once as usual and it will remind you in the memory summary section of the output)

For the following line:

auto x = new int(5);

Valgrind will output the following:

==2881== 4 bytes in 1 blocks are definitely lost in loss record 1 of 1
==2881==    at 0x4C2B0E0: operator new(unsigned long) (in /usr/lib/valgrind/vgp
reload_memcheck-amd64-linux.so)
==2881==    by 0x400966: main (test.cpp:92)

Valgrind is used for function and memory analysis:

Not only can Valgrind tell you where the error is, it can also help you optimize your code. People often think that they know what causes the program to consume a lot of memory when it is running..., only to find out that it is wrong after a lot of tossing. How can you save your precious time? Measure more.

Run your program with the following command:

valgrind --tool=callgrind ./myProgram

It will generate a file like "callgrind.out.2887" in the directory of the program under test. Download the program KCachegrind, which provides a visual interface showing the execution path of your program and which function is eating up your precious memory. At a glance, you quickly know where to focus your firepower.

Here is some simple output example that lists the time consumption (wall time), memory (percentage) consumption and number of calls for each function. Google it and you can find a lot of other interesting graphs it generates.

We can also use the --tool=massif parameter to discover memory-intensive code. It was originally used to detect memory leaks, but memory leaks can cause a lot of memory to stay in the program.

Conclusion:

Valgrind is much more than a memory leak detection tool. It's time to change your mind: Valgrind is to be a scavenger of unknown behavior.

Valgrind is perfectly fine as your tool of choice. Not only will he tell you where and why the error is, but the point is he will warn you before the program crashes (both things GDB can't do). Of course GDB is still excellent, it can give full and detailed stack traces on assertion failures, which is necessary for debugging concurrent code and some other situations.

-pedantic -Wall -Wextra These compile options are also quite useful. Smarter modern compilers can also help you locate some unknown behavior. Valgrind should be seen as a strong complement to the compiler, not as a functionally overlapping competitor.

If you're interested in an advance, you can also take a look at the following tools, which do more or less the same job as Valgrind, with less impact on the runtime of the program:

Address Sanitizer for clang and g++
Undefined Behavior Sanitizer for clang and g++
Memory Sanitizer for clang
Thread Sanitizer for clang

quote

https://www.oschina.net/translate/valgrind-is-not-a-leak-checker

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325813051&siteId=291194637