I'm trying to use -fsanitize=address with gcc.
I declare global variable(ex. int*) and allocate memory with malloc, then I didn't call free function. I expect the sanitizer will show up error message about memory leak, but it exit with no error message.
So, I use local variable for test. Sanitizer works well on that test code. I put my codes below.
this is a global variable code.
#include<stdio.h>
#include<stdlib.h>
int *gv;
int main(){
gv = (int*)malloc(sizeof(int)*4);
printf("yooooolooooooo\n");
return 0;
}
and this is local variable code.
#include<stdio.h>
#include<stdlib.h>
int main(){
int *gv = (int*)malloc(sizeof(int)*4);
printf("yooooolooooooo\n");
return 0;
}
I compile with gcc -fsanitize=address -o test test.c.
upper code shows me just
yooooolooooooo
but local variable code shows
yooooolooooooo
=================================================================
==15484==ERROR: LeakSanitizer: detected memory leaks
Direct leak of 16 byte(s) in 1 object(s) allocated from:
#0 0x7f6e43395b60 in __interceptor_malloc (/usr/lib/x86_64-linux-gnu/libasan.so.5+0xedb60)
#1 0x564b5f8bd936 in main (/home/jiho/lab/test+0x936)
#2 0x7f6e42ed8b96 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x21b96)
SUMMARY: AddressSanitizer: 16 byte(s) leaked in 1 allocation(s).
I wonder why sanitizer doesn't works with global variable...
thank you and i hope someone knows about it.
ps. my system is ubuntu 18.04 and x86_64.
Similar to Valgrind LeakSanitizer reports only "direct" leaks i.e. addresses that are no longer accessible from any existing user data (called "root-set" in LSan design document). In case of global variable the address is obviously still accessible.
Related
I use the following code to test the pointer returned by getenv, if not free, testing will cause a memory leak.
#include <stdio.h>
#include <stdlib.h>
void demo() {
const char *home = getenv("HOME");
printf("%s\n", home);
}
int main() {
demo();
return 0;
}
I use Valgrind to detect memory leaks:
$ gcc main.c -o main
$ valgrind --tool=memcheck --leak-check=full --show-leak-kinds=all ./main
The result is as follows:
==134679== Memcheck, a memory error detector
==134679== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==134679== Using Valgrind-3.18.1 and LibVEX; rerun with -h for copyright info
==134679== Command: ./demo
==134679==
/home/aszswaz
==134679==
==134679== HEAP SUMMARY:
==134679== in use at exit: 0 bytes in 0 blocks
==134679== total heap usage: 1 allocs, 1 frees, 1,024 bytes allocated
==134679==
==134679== All heap blocks were freed -- no leaks are possible
==134679==
==134679== For lists of detected and suppressed errors, rerun with: -s
==134679== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
It shows that a piece of memory has been applied for and a piece of memory has been reclaimed. Why can the pointer obtained with getenv be automatically recycled?
If I add free((void *)home) to the code, will it affect the global environment variable?
https://man7.org/linux/man-pages/man3/getenv.3.html does not mention anything that getenv()'s return value is allocated on the heap. Hence, think of it like argv.
Although rarely and myself never seen this happen, in some implementations, getenv() may allocate memory on the heap and return a pointer to it. Therefore, its best to review your operating system's manual pages.
Only memory allocated on the heap must be free()d.
And, pointer to memory allocated on the heap is usually only returned when you call malloc(), calloc(), realloc(), strdup(), etc.
Anyways:
If you do call free() on the pointer returned by getenv(), it is undefined behavior if your operating system did not return a pointer to memory on heap.
And whoa, I almost didn't see this!:
I think this is what is confusing you:
usage: 1 allocs, 1 frees
printf() may allocate on heap and free it before returning back.
Edit:
Using gdb, we can find that:
#0 __GI___libc_malloc (1024) // and indeed it calls with 1024 bytes
#1 __GI__IO_file_doallocate ()
#2 __GI__IO_doallocbuf ()
#4 _IO_new_file_xsputn ()
#5 _IO_new_file_xsputn ()
#6 __GI__IO_puts
#7 demo ()
#8 main ()
printf() seems to be replaced by puts(), and puts() calls malloc() through a long function call chain.
But, it doesn't seem to call free(). I think it calls some other function that frees the memory. I'm still doing my research.
The environment is initialized on the initial process stack, just above the parameters argc and argv to main().
Normally, as the environment life and parameters themselves is the whole program life, the common implementation consist in pushing the environment strings, the environment array, the main function parameter strings and the command line array in the stack, just before pushing the three variables (historically main() had a third parameter environ, that was passed also to main) For legacy code reasons, this environment pointer is still passed to main.
Just try the following program:
#include <stdio.h>
#include <string.h>
int main(int argc, char **argv, char **environ)
{
printf("argc = %d\n", argc);
printf("args:");
for (int i = 0; i < argc; i++) {
printf(" [%s]", argv[i]);
}
printf("\n\nEnvironment:\n");
for (char **p = environ; *p; p++) {
printf(" %s\n", *p);
}
return 0;
}
what getenv() returns is not statically allocated data, nor dynamically allocated data in the heap. It is stored in the stack (almost all POSIX-like operating systems solve this problem in the same way) just above main() parameters (you can check it with a debugger, to see where are exactly located the strings, I have already done too)
So your problem is not related to getenv(), which doesn't use malloc() to return dynamically allocated strings to you.
You'll need to look elsewhere for that memory Valgrind identifies as dynamic (It doesn't show any problem with it)
Think that valgrind identifies the memory you have allocated with malloc() (or any of its friends) and not free()d, and the report says at exit() there's no memory allocated. So getenv() doesn't allocate memory to give you the environment contents.
If you read on how the kernel initializes a process memory's initial stack, you will find very interesting things (listed from most deep in the stack to the top of it):
There's some fixed machine code to properly return from kernel mode when there's a pending interrupt (interrupts are executed only when the kernel switches from kernel mode to user mode because they have to execute the handler code in user mode, and never in kernel mode, for obvious reasons)
There's a legacy structure to save command line parameters and to allow the kernel to access command parameters for ps(1) command to work. This is no longer true, as it represents a security hole to put kernel info in user space, but the structure is still there, for legacy code to use. The parameters for a command are now in kernel space, and can only be modified by a special system call.
There are all the strings associated to the received environment of the process.
The array of pointer to the environment strings is also stored in the stack. This includes a final NULL pointer to be able to identify the end of the array.
All the strings of the command line parameters are also stored there.
The array of pointers (including the last NULL pointer, which is not counted in argc) of the command line parameters.
The parameter environ with a pointer to the array of environment strings.
The parameter argv with a pointer to the array of command line parameters.
The parameter argc with the number of command line parameters (this includes the program name, but excludes the last NULL pointer)
I'm scratching my head trying to figure out why ASAN isn't picking up on a simple memory leak. valgrind finds it just fine. Help?
Example that ASAN does find.
#include <stdlib.h>
#include <stdio.h>
void blah(void)
{
int *some_int = malloc(sizeof(int));
*some_int = 1;
printf("hello %p\n", some_int);
// some_int is lost here
}
int main()
{
blah();
return 0;
}
mbryan#remotedev-mbryan:~/git/mbryan/onefs$ clang -fsanitize=address -O0 q.c
mbryan#remotedev-mbryan:~/git/mbryan/onefs$ ./a.out
hello 0x602000000010
=================================================================
==10751==ERROR: LeakSanitizer: detected memory leaks
Direct leak of 4 byte(s) in 1 object(s) allocated from:
#0 0x4d9bd0 in malloc (/ifs/home/mbryan/git/mbryan/onefs/a.out+0x4d9bd0)
#1 0x5120f3 in blah (/ifs/home/mbryan/git/mbryan/onefs/a.out+0x5120f3)
#2 0x512183 in main (/ifs/home/mbryan/git/mbryan/onefs/a.out+0x512183)
#3 0x7f3515000b96 in __libc_start_main /build/glibc-OTsEL5/glibc-2.27/csu/../csu/libc-start.c:310
SUMMARY: AddressSanitizer: 4 byte(s) leaked in 1 allocation(s).
So far so good. Now print the value instead of the pointer:
#include <stdlib.h>
#include <stdio.h>
void blah(void)
{
int *some_int = malloc(sizeof(int));
*some_int = 1;
printf("hello %d\n", *some_int); // <---------------
}
int main()
{
blah();
return 0;
}
mbryan#remotedev-mbryan:~/git/mbryan/onefs$ clang -fsanitize=address -O0 q.c
mbryan#remotedev-mbryan:~/git/mbryan/onefs$ ./a.out
hello 1
...now the leak doesn't show up.
On the latter if I recompile without the sanitizer and run valgrind, valgrind does indeed show a leak:
==10782== definitely lost: 4 bytes in 1 blocks
Looking at the assembly: I see the optimimzer hasn't made my malloc'd variable a local or some other trickery. So: why isn't AddressSanitizer picking up this one? Am I missing something obvious?
This is on Ubuntu18.04 using clang 6.0.0-1ubuntu2.
I've been informed from the ASAN folks that this is a known bug:
https://github.com/google/sanitizers/issues/937
LeakSanitizer: false negative when functions stack frames overlay #937
I am writing a pet project (a sort of single-threaded Lisp-like language interpreter in C), and I came upon the following issue: a pointer is overwritten while malloc() is run. Showing all the code would be too long, but I can share it if necessary. I would like to have some insight regarding how the problem can be debugged.
The bug happens during the subroutine that runs a user-defined function:
/* Determine the amount of arguments to the called function */
int argc = ast_len(fn_args)
printf("%s:%d %p\n", __FILE__, __LINE__, (void*) scope->vars->tail);
/* Allocate the memory to store the array of pointers to each of the arguments */
struct YL_Var** argv = malloc(sizeof(struct YL_Var*)*argc);
printf("%s:%d %p\n", __FILE__, __LINE__, (void*) scope->vars->tail);
You will get the following output:
interpreter.c:549 0x5558371c9480
interpreter.c:551 0x411
Segmentation fault (core dumped)
The pointer scope->vars->tail is overwritten during the call to malloc()!
Using gdb and hardware break points clearly showed that the value was overwritten inside malloc.c.
As the pointer is overwritten, the program segfaults soon after, both within gdb and in normal run. However it does not segfault when run inside valgrind, it even ends successfully.
So here is my question. How would you start to debug this mess? I am asking for advice, and not for an answer.
I am far from being a C expert :)
I guess I am at fault and this is of course no bug in glibc-2.26 or gcc 7.2.0.
I have no warnings on gcc with -Wall -Wextra -Wpedantic
valgrind shows some unfreed memory issue. Will fix them before doing anything else.
Thanks to everyone's comment, I found the issue.
scope->vars was not appropriately allocated (as some said).
When using valgrind, I found the following message:
==23054== Invalid write of size 8
==23054== at 0x10A380: varlist_prepend (interpreter.c:277)
==23054== by 0x109548: main (yl.c:39)
==23054== Address 0x5572a98 is 0 bytes after a block of size 8 alloc'd
==23054== at 0x4C2FB0F: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==23054== by 0x10A304: varlist_prepend (interpreter.c:274)
==23054== by 0x109548: main (yl.c:39)
==23054==
My code looked like this:
struct YL_VarList* vars = malloc(sizeof(vars));
As you can see, the * was missing.
This is the corrected version:
struct YL_VarList* vars = malloc(sizeof(*vars));
sizeof(vars) would return the size of struct YL_VarList*, while I want to allocate the size of struct YL_VarList.
I use this code snippet:
// stackoverflow.c
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
int main(int argc, char** argv)
{
int i;
int a[10];
// init
a[-1] = -1;
a[11] = 11;
printf(" a[-1]= = %d, a[11] = %d\n", a[-1], a[11]);
printf("I am finished.\n");
return a[-1];
}
The compiler is GCC for linux x86. It works well without any run-time error. I also test this code in Valgrind, which don't trigger any memory error either.
$ gcc -O0 -g -o stack_overflow stack_overflow.c
$ ./stack_overflow
a[-1]= = -1, a[11] = 11
I am finished.
$ valgrind ./stack_overflow
==3705== Memcheck, a memory error detector
==3705== Copyright (C) 2002-2013, and GNU GPL'd, by Julian Seward et al.
==3705== Using Valgrind-3.10.0.SVN and LibVEX; rerun with -h for copyright info
==3705== Command: ./stack_overflow
==3705==
a[-1]= = -1, a[11] = 11
I am finished.
==3705==
==3705== HEAP SUMMARY:
==3705== in use at exit: 0 bytes in 0 blocks
==3705== total heap usage: 0 allocs, 0 frees, 0 bytes allocated
==3705==
==3705== All heap blocks were freed -- no leaks are possible
==3705==
==3705== For counts of detected and suppressed errors, rerun with: -v
==3705== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
From my understanding, heap and stack is the same kind of memory. The only difference is that they grow in the opposite direction.
So my question is:
Why heap overflow/underflow will trigger an rum-time error, while stack overflow/underflow will not?
why C language designer didn't take this into account just like heap, other than leave it Undefined Behaviour
valgrind does not detect stack buffer overflows. Use AddressSanitizer. At least gcc 4.8 is required and libasan must be installed.
gcc -g -fsanitize=address stackbufferoverflow.c
==1955==ERROR: AddressSanitizer: stack-buffer-underflow on address 0x7fffff438d4c at pc 0x000000400a1d bp 0x7fffff438d10 sp 0x7fffff438d00
WRITE of size 4 at 0x7fffff438d4c thread T0
#0 0x400a1c in main /home/m/stackbufferoverflow.c:9
#1 0x7fe7e24e178f in __libc_start_main (/lib64/libc.so.6+0x2078f)
#2 0x400888 in _start (/home/m/a.out+0x400888)
Address 0x7fffff438d4c is located in stack of thread T0 at offset 28 in frame
#0 0x400965 in main /home/m/stackbufferoverflow.c:5
This frame has 1 object(s):
[32, 72) 'a' <== Memory access at offset 28 underflows this variable
HINT: this may be a false positive if your program uses some custom stack unwind mechanism or swapcontext
(longjmp and C++ exceptions *are* supported)
SUMMARY: AddressSanitizer: stack-buffer-underflow /home/m/stackbufferoverflow.c:9 main
why C language designer didn't take this into account just like heap, other than leave it Undefined Behaviour
The original C langauge designers wrote a kind of more comfortable and portable assembler for themselves. The original language has not been designed to be bullet-proof against programmers' errors.
If you are interested in an opposite example then look at Ada (http://en.wikipedia.org/wiki/Ada_%28programming_language%29).
C doesn't check things like out-of-bounds array indexing. It just does what you told it to, in this case to change element number 11 in an array of 10 elements. Typically, this means that your program writes to the location in memory where this item should have been stored, if it had existed. This may or may not cause some sort of visible error, such a crash. It might have no effect, or it could make your program do something strange. It depends on what, if anything, happened to be stored at that place in memory, and how it is used.
Some other programming languages do perform checks such as these, and guarantee that an error will be reported. The C standard gives no such guarantees, and just says that it will cause "undefined behaviour". One reason for this is that it should be possible to write very efficient programs in C, where checks would cause a small, but in some cases perhaps unacceptable, delay. Also, back when C was designed, computers were slower, and the delay would have been a worse problem.
There is also no guarantee in C that heap errors will be detected or reported. Valgrind is not part of the C language, but a different tool, and it does its best to find errors using other and more effective mechanisms than C would, but there is no guarantee that it will find all errors.
EDIT
Here's an interesting tuto:
http://gribblelab.org/CBootcamp/7_Memory_Stack_vs_Heap.html
BTW Clang (OSX) detects it, but it's just and extra feature, good old gcc would let you do it.
ctest.c:6:5: warning: array index 42 is past the end of the array (which contains 1 element) [-Warray-bounds]
a[42] = 42;
^ ~~
cpp.cpp:4:5: note: array 'a' declared here
int a[1];
^
1 warning generated.
Old
a[11] = 11;
Would trigger a Segmentation fault (but here it's only one byte it's just overriding the value of another variable, most likely), if you want a stack overflow try something that does an infinite recursion.
Also if you want to make your code segfault proof (for malloc only) I suggest you compile it with electric fence for your tests. It will prevent your program to go above its allocated memory (starting from the first byte)
http://linux.die.net/man/3/efence
As suggested in the comments Valgrind is also a useful tool.
http://valgrind.org/
Why does not stack overflow/underflow trigger an run-time error?
C is not limited to "heap" and "stack" implementations. Example: Variables in main() need not be in a "stack". Even GCC may optimize in way that defy a simple understanding. Many memory architectures are possible. Since C does not specify the underlying memory architecture, the following is simply undefined behavior. #Karoly Horvath
// Undefined behavior: accessing memory outside array's range.
int a[10];
a[-1] = -1;
a[11] = 11;
Any analysis may make sense with a given memory model on a given day of the week, but that behavior is just one of many possibilities.
Allocating heap storage always includes a test for insufficient memory; for stack space this is less critical due to the way stack space is reused over and over again. If they share the same block of storage, then they could collide.
GCC won't do this because heap space and stack space are separate; I don't know about Valgrind.
In at least one old language (Turbo C), an alloc() will fail if less than 256 bytes of storage remain between top-of-heap and bottom-of-stack. It is assumed 256 bytes is enough to accommodate stack growth. If it's not, you get some very weird run-time errors.
Turbo C has a compile-time option, -N, to check for stack overflow more thoroughly. Other languages may have a similar option.
How do I use valgrind to find the memory leaks in a program?
Please someone help me and describe the steps to carryout the procedure?
I am using Ubuntu 10.04 and I have a program a.c, please help me out.
How to Run Valgrind
Not to insult the OP, but for those who come to this question and are still new to Linux—you might have to install Valgrind on your system.
sudo apt install valgrind # Ubuntu, Debian, etc.
sudo yum install valgrind # RHEL, CentOS, Fedora, etc.
sudo pacman -Syu valgrind # Arch, Manjaro, Garuda, etc
Valgrind is readily usable for C/C++ code, but can even be used for other
languages when configured properly (see this for Python).
To run Valgrind, pass the executable as an argument (along with any
parameters to the program).
valgrind --leak-check=full \
--show-leak-kinds=all \
--track-origins=yes \
--verbose \
--log-file=valgrind-out.txt \
./executable exampleParam1
The flags are, in short:
--leak-check=full: "each individual leak will be shown in detail"
--show-leak-kinds=all: Show all of "definite, indirect, possible, reachable" leak kinds in the "full" report.
--track-origins=yes: Favor useful output over speed. This tracks the origins of uninitialized values, which could be very useful for memory errors. Consider turning off if Valgrind is unacceptably slow.
--verbose: Can tell you about unusual behavior of your program. Repeat for more verbosity.
--log-file: Write to a file. Useful when output exceeds terminal space.
Finally, you would like to see a Valgrind report that looks like this:
HEAP SUMMARY:
in use at exit: 0 bytes in 0 blocks
total heap usage: 636 allocs, 636 frees, 25,393 bytes allocated
All heap blocks were freed -- no leaks are possible
ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
I have a leak, but WHERE?
So, you have a memory leak, and Valgrind isn't saying anything meaningful.
Perhaps, something like this:
5 bytes in 1 blocks are definitely lost in loss record 1 of 1
at 0x4C29BE3: malloc (vg_replace_malloc.c:299)
by 0x40053E: main (in /home/Peri461/Documents/executable)
Let's take a look at the C code I wrote too:
#include <stdlib.h>
int main() {
char* string = malloc(5 * sizeof(char)); //LEAK: not freed!
return 0;
}
Well, there were 5 bytes lost. How did it happen? The error report just says
main and malloc. In a larger program, that would be seriously troublesome to
hunt down. This is because of how the executable was compiled. We can
actually get line-by-line details on what went wrong. Recompile your program
with a debug flag (I'm using gcc here):
gcc -o executable -std=c11 -Wall main.c # suppose it was this at first
gcc -o executable -std=c11 -Wall -ggdb3 main.c # add -ggdb3 to it
Now with this debug build, Valgrind points to the exact line of code
allocating the memory that got leaked! (The wording is important: it might not
be exactly where your leak is, but what got leaked. The trace helps you find
where.)
5 bytes in 1 blocks are definitely lost in loss record 1 of 1
at 0x4C29BE3: malloc (vg_replace_malloc.c:299)
by 0x40053E: main (main.c:4)
Techniques for Debugging Memory Leaks & Errors
Make use of www.cplusplus.com! It has great documentation on C/C++ functions.
General advice for memory leaks:
Make sure your dynamically allocated memory does in fact get freed.
Don't allocate memory and forget to assign the pointer.
Don't overwrite a pointer with a new one unless the old memory is freed.
General advice for memory errors:
Access and write to addresses and indices you're sure belong to you. Memory
errors are different from leaks; they're often just IndexOutOfBoundsException
type problems.
Don't access or write to memory after freeing it.
Sometimes your leaks/errors can be linked to one another, much like an IDE discovering that you haven't typed a closing bracket yet. Resolving one issue can resolve others, so look for one that looks a good culprit and apply some of these ideas:
List out the functions in your code that depend on/are dependent on the
"offending" code that has the memory error. Follow the program's execution
(maybe even in gdb perhaps), and look for precondition/postcondition errors. The idea is to trace your program's execution while focusing on the lifetime of allocated memory.
Try commenting out the "offending" block of code (within reason, so your code
still compiles). If the Valgrind error goes away, you've found where it is.
If all else fails, try looking it up. Valgrind has documentation too!
A Look at Common Leaks and Errors
Watch your pointers
60 bytes in 1 blocks are definitely lost in loss record 1 of 1
at 0x4C2BB78: realloc (vg_replace_malloc.c:785)
by 0x4005E4: resizeArray (main.c:12)
by 0x40062E: main (main.c:19)
And the code:
#include <stdlib.h>
#include <stdint.h>
struct _List {
int32_t* data;
int32_t length;
};
typedef struct _List List;
List* resizeArray(List* array) {
int32_t* dPtr = array->data;
dPtr = realloc(dPtr, 15 * sizeof(int32_t)); //doesn't update array->data
return array;
}
int main() {
List* array = calloc(1, sizeof(List));
array->data = calloc(10, sizeof(int32_t));
array = resizeArray(array);
free(array->data);
free(array);
return 0;
}
As a teaching assistant, I've seen this mistake often. The student makes use of
a local variable and forgets to update the original pointer. The error here is
noticing that realloc can actually move the allocated memory somewhere else
and change the pointer's location. We then leave resizeArray without telling
array->data where the array was moved to.
Invalid write
1 errors in context 1 of 1:
Invalid write of size 1
at 0x4005CA: main (main.c:10)
Address 0x51f905a is 0 bytes after a block of size 26 alloc'd
at 0x4C2B975: calloc (vg_replace_malloc.c:711)
by 0x400593: main (main.c:5)
And the code:
#include <stdlib.h>
#include <stdint.h>
int main() {
char* alphabet = calloc(26, sizeof(char));
for(uint8_t i = 0; i < 26; i++) {
*(alphabet + i) = 'A' + i;
}
*(alphabet + 26) = '\0'; //null-terminate the string?
free(alphabet);
return 0;
}
Notice that Valgrind points us to the commented line of code above. The array
of size 26 is indexed [0,25] which is why *(alphabet + 26) is an invalid
write—it's out of bounds. An invalid write is a common result of
off-by-one errors. Look at the left side of your assignment operation.
Invalid read
1 errors in context 1 of 1:
Invalid read of size 1
at 0x400602: main (main.c:9)
Address 0x51f90ba is 0 bytes after a block of size 26 alloc'd
at 0x4C29BE3: malloc (vg_replace_malloc.c:299)
by 0x4005E1: main (main.c:6)
And the code:
#include <stdlib.h>
#include <stdint.h>
int main() {
char* destination = calloc(27, sizeof(char));
char* source = malloc(26 * sizeof(char));
for(uint8_t i = 0; i < 27; i++) {
*(destination + i) = *(source + i); //Look at the last iteration.
}
free(destination);
free(source);
return 0;
}
Valgrind points us to the commented line above. Look at the last iteration here,
which is *(destination + 26) = *(source + 26);. However, *(source + 26) is
out of bounds again, similarly to the invalid write. Invalid reads are also a
common result of off-by-one errors. Look at the right side of your assignment
operation.
The Open Source (U/Dys)topia
How do I know when the leak is mine? How do I find my leak when I'm using
someone else's code? I found a leak that isn't mine; should I do something? All
are legitimate questions. First, 2 real-world examples that show 2 classes of
common encounters.
Jansson: a JSON library
#include <jansson.h>
#include <stdio.h>
int main() {
char* string = "{ \"key\": \"value\" }";
json_error_t error;
json_t* root = json_loads(string, 0, &error); //obtaining a pointer
json_t* value = json_object_get(root, "key"); //obtaining a pointer
printf("\"%s\" is the value field.\n", json_string_value(value)); //use value
json_decref(value); //Do I free this pointer?
json_decref(root); //What about this one? Does the order matter?
return 0;
}
This is a simple program: it reads a JSON string and parses it. In the making,
we use library calls to do the parsing for us. Jansson makes the necessary
allocations dynamically since JSON can contain nested structures of itself.
However, this doesn't mean we decref or "free" the memory given to us from
every function. In fact, this code I wrote above throws both an "Invalid read"
and an "Invalid write". Those errors go away when you take out the decref line
for value.
Why? The variable value is considered a "borrowed reference" in the Jansson
API. Jansson keeps track of its memory for you, and you simply have to decref
JSON structures independent of each other. The lesson here:
read the documentation. Really. It's sometimes hard to understand, but
they're telling you why these things happen. Instead, we have
existing questions about this memory error.
SDL: a graphics and gaming library
#include "SDL2/SDL.h"
int main(int argc, char* argv[]) {
if (SDL_Init(SDL_INIT_VIDEO|SDL_INIT_AUDIO) != 0) {
SDL_Log("Unable to initialize SDL: %s", SDL_GetError());
return 1;
}
SDL_Quit();
return 0;
}
What's wrong with this code? It consistently leaks ~212 KiB of memory for me. Take a moment to think about it. We turn SDL on and then off. Answer? There is nothing wrong.
That might sound bizarre at first. Truth be told, graphics are messy and sometimes you have to accept some leaks as being part of the standard library. The lesson here: you need not quell every memory leak. Sometimes you just need to suppress the leaks because they're known issues you can't do anything about. (This is not my permission to ignore your own leaks!)
Answers unto the void
How do I know when the leak is mine?
It is. (99% sure, anyway)
How do I find my leak when I'm using someone else's code?
Chances are someone else already found it. Try Google! If that fails, use the skills I gave you above. If that fails and you mostly see API calls and little of your own stack trace, see the next question.
I found a leak that isn't mine; should I do something?
Yes! Most APIs have ways to report bugs and issues. Use them! Help give back to the tools you're using in your project!
Further Reading
Thanks for staying with me this long. I hope you've learned something, as I tried to tend to the broad spectrum of people arriving at this answer. Some things I hope you've asked along the way: How does C's memory allocator work? What actually is a memory leak and a memory error? How are they different from segfaults? How does Valgrind work? If you had any of these, please do feed your curiousity:
More about malloc, C's memory allocator
Definition of a segmentation fault
Definition of a memory leak
Definition of a memory access error
How does Valgrind work?
Try this:
valgrind --leak-check=full -v ./your_program
As long as valgrind is installed it will go through your program and tell you what's wrong. It can give you pointers and approximate places where your leaks may be found. If you're segfault'ing, try running it through gdb.
You can run:
valgrind --leak-check=full --log-file="logfile.out" -v [your_program(and its arguments)]
You can create an alias in .bashrc file as follows
alias vg='valgrind --leak-check=full -v --track-origins=yes --log-file=vg_logfile.out'
So whenever you want to check memory leaks, just do simply
vg ./<name of your executable> <command line parameters to your executable>
This will generate a Valgrind log file in the current directory.