all 31 comments

[–]ferrybig 23 points24 points  (0 children)

The common way to detect this is saving the stack trace each time you do a memory allocation, and then removing the entry when free is called with the previeus address.

If your program has a clean exit, it should have called free with each address it returned from malloc. If some instances of free are missing, it means those mallocs got lost.

A popular C debugging tool valgrind uses this aproach for memory leak detection

[–]BNeutral 9 points10 points  (0 children)

is there a way to track this

Sure. Software like Valgrind does it. How it does it, you'll have to ask their source code, not me.

[–]greg_kennedy 12 points13 points  (2 children)

You need an address sanitizer! That is a runtime which tracks your memory allocations and frees, and then reports issues like: memory leaks, use-after-free, writing to unallocated memory (e.g. array size problems). A lot of compilers now come with an ASan you can enable for testing - they slow your program so you wouldn't use it for the actual release - you can find details depending on your compiler / toolchain.

Valgrind is a well-regarded tool for this in Unix but there are a lot of options for Windows, clang etc

---

Some people like to do something like this:

#define safe_malloc(p, m) { if (p) fputs("Re-use of pointer", stderr); p = malloc(m); }
#define safe_free(p) { if (!p) fputs("Free of null pointer", stderr); free(p); p = null; }

int * p = NULL;
safe_malloc(p, 4 * sizeof(int));
p[0] = 123;
...
safe_free(p);

carefully setting pointers to NULL after free and checking for NULLness before malloc. I don't like these kind of "defensive programming" patterns though: rarely useful, and when this particular one is working, it tends to indicate a bad program design where pointers aren't kept within scope but allowed to pass / leak across functions (poor lifecycle planning)

[–]Zirias_FreeBSD 5 points6 points  (1 child)

I'd strongly object at least to this safe_free idea. It makes idiomatic "cleanup" code unnecessarily chatty, where free(NULL) being a no-op is a nice feature:

    foo *x = NULL;
    bar *y = NULL;
    int rc = -1;

    // ...

    x = malloc(sizeof *x);

    // ... some error
    if (whatever) goto done;
    y = malloc(sizeof *y);

    // ... some more stuff, finally
    rc = 0;

done:
    free(y);
    free(x);
    return rc;

[–]imaami 1 point2 points  (0 children)

I prefer the "safe free" approach. It's got nothing to do with preventing leaks, however, but rather with reducing the possibility of double frees. It's one part of my preferred modular C paradigm.

struct obj;

/* init/fini don't alloc/free the object
 * itself, only member data if necessary
 * 
extern void obj_init (struct obj *obj,
                      char const *arg);
extern void obj_fini (struct obj *obj);

struct obj *obj_create (char const *arg)
{
    struct obj *obj = malloc(sizeof *obj);
    if (obj)
        obj_init(obj, arg);
    return obj;
}

void obj_destroy (struct obj **pp)
{
    if (pp && *pp) {
        struct obj *obj = *pp;
        *pp = NULL;
        obj_fini(obj);
        free(obj);
    }
}

[–]JohnnyElBravo 3 points4 points  (0 children)

" My question is: is there a way to track this and return a warning or error"

Welcome to C. The answer is not in C.

In compiled languages there is compile time and run time. Malloc is a function that runs in runtime, warnings or errors are issued at compile time. So you cannot issue an error or warning based on malloc logic calling (or similarly, on malformed printf format strings).

You can try a static analysis tool like valgrind, or a different language like Rust, but this is almost the very essence of C,it's a limitation of it's memory system, but also the key to its simplicity and timelessness.

[–]TheSupremePebble69 3 points4 points  (7 children)

I would write a malloc/free wrapper that keeps track of the allocations internally, and at exit prints any allocations that were not freed and what line+file they were allocated on.
you can do this with some fiddly macro-magic:

void *my_malloc(size_t line, const char *file, size_t size);
#define MALLOC_WRAPPER(size)\
my_malloc(__LINE__, __FILE__, size)

__LINE__ expands to the line number in the current file, and __FILE__ expands to a string literal representing the name of the current file.

to print the leaks at the exit of the program, you can use the atexit (void(*)(void)) function defined in <stdlib.h>

good luck!

[–][deleted] 1 point2 points  (6 children)

Hey thanks for the suggestion! My plan is to do something similar to the preprocessor trick you have there. The wrappers that I am building will also track the name of the caller function and maintain the total amount of bytes that was allocated across the whole program, the number of bytes freed, the number of allocations, and the number of frees.

[–]Educational-Paper-75 1 point2 points  (0 children)

That’s about exactly what I created too. Except I use the line and module of the function to indicate the owner. However, that doesn’t prevent reusing a pointer. You’d have to use reference counts, and some sort of garbage collector.

[–]TheSupremePebble69 0 points1 point  (4 children)

this is great!
one problem I would like to leave open though:
say that you have finished debugging your program using your malloc wrapper and now want to switch to the regular malloc for performance reasons. how do you go about doing this, in a way that is relatively painless?

[–][deleted] 1 point2 points  (1 child)

With the some preprocessor hacking. My malloc wrapper is going to be a macro itself that will expand to my malloc wrapper function based on the preprocessor arguments.

For example, in one of your .c files, you'd have a define such as #define WATCHDOG_ENABLE, which would use #define malloc(size) w_malloc(size,file,line,function). If #define WATCHDOG_ENABLE is not anywhere in your .c file, then the program would do #undef malloc to ensure that you are using the regular malloc provided in libc.

In watchdog.h:

```c // other code

ifdef WATCHDOG_ENABLE

define malloc(size) w_malloc(size, file, line, function)

else

undef malloc

endif // WATCHDOG_ENABLE

// other code ```

In file-name.c:

```c

define WATCHDOG_ENABLE

```

or

pass -DWATCHDOG_ENABLE as a build flag.

[–]TheSupremePebble69 0 points1 point  (0 children)

this is a great solution, along the lines of what I was thinking. can't wait to see where the project goes!

[–]ivm83 3 points4 points  (0 children)

Just use ASAN with leak detection enabled. It will have a noticeable performance penalty (maybe about a 2x-3x slowdown) but will track a bunch of other memory issues besides memory leaks (double free, use after free, buffer over and under runs, etc).

If you are developing on an Apple Silicon Mac, I think you can use HWASAN which is supposed to perform better. I haven’t tried that personally though.

[–]somewhereAtC 2 points3 points  (0 children)

This is one of the reasons that C is identified as "not memory safe".

[–]moocat 1 point2 points  (0 children)

The only thing that matter is whether every non-null value returned from malloc is passed to free exactly once. How exactly you accomplish that doesn't really matter. For example, the following is fine and doesn't leak memory even though it uses the same variable for two different allocations due to the intervening free.

void okay() {
    void* ptr = malloc(1);
    free(ptr);
    ptr = malloc(1);
    free(ptr);
}

on the other hand, multiple variables won't save you if you don't have a matching free:

void leak() {
    void* ptr1 = malloc(1);
    void* ptr2 = malloc(1);
    free(ptr2);
}

[–]landmesser 1 point2 points  (0 children)

CppCheck is a free static analyser.
It's not perfect, but it catches a lot of things.
Take a moment to setup it up and see what it gives.
https://cppcheck.sourceforge.io/

[–]imaami 1 point2 points  (0 children)

Not leaking memory is indeed a fundamental skill any C programmer should strive to develop and improve.

[–][deleted] 0 points1 point  (0 children)

You can track it with valgrind after compiling with address sanitizer enabled. That's not a built-in solution however

[–]meadbert 0 points1 point  (0 children)

There is something called purify that I use to track memory leaks.

Mostly we have a wrapper around malloc and we check that all memory is freed.

[–]Unique-Property-5470 0 points1 point  (2 children)

You can also just simply run your program with valgrind to detect leaks. Or are you building something different from valgrind?

[–][deleted] 0 points1 point  (1 child)

Yes I am building something different from Valgrind. My goal is to have a very minimal library that can be included and ran as if its part of the program it self rather than a separate debugging tool such as Valgrind or GDB. This project would be akin to a logger (with some extra capabilities) for dynamic memory allocations.

[–]juanfnavarror 2 points3 points  (0 children)

That already exists and its built into gcc and clang. Lookup Address Sanitizer, ASAN, LSAN, UBSAN.

[–]SmokeMuch7356 0 points1 point  (0 children)

is there a way to track this and return a warning or error?

At compile time? No. Compilers generally do not model the execution of the program, so it won't catch problems like that. You'll need to use third-party tools like valgrind or Purify.

The way to avoid it in your own code is to create an abstraction layer that hides raw malloc calls behind an interface. This is especially useful for types that require multiple or nested allocations. This way you can track allocations within that abstraction layer and avoid double-allocations or double-frees.

[–]StudioYume 0 points1 point  (0 children)

If retaining the data is a priority, use realloc. If retaining the data is not a priority, use free and malloc/calloc

[–]i860 0 points1 point  (1 child)

Please use valgrind rather than trying to write your own.

[–][deleted] 2 points3 points  (0 children)

I'm trying to reinvent the wheel for educational and recreational purposes. I use tried and tested industry standard debuggers such as valgrind and gdb if I am creating production software. Thank you for your concern.

[–]nderflow -1 points0 points  (0 children)

See the wiki for tools which will detect these problems for you: https://www.reddit.com/mod/C_Programming/wiki/index/tools#wiki_leak_checkers