Hello!
I made a short piece of code that I find very useful for debugging,
and I wanted to ask you if it is correct, somehow acceptable or if I
simply reinvented the wheel.
Yes, you are reinventing the wheel. However, the C standard
practically *demands* that you re-invent the wheel, because modern
systems require that you make numerous complicated decisions in
implementing this that the C standard does not address. (I.e., the
right answer is that the C standard should simply *provide* greater
dynamic memory functionality which makes this either easier to
implement or fall trivially out of the C standard. Force the compiler
vendors to make the platform more portable, not developers.)
To deal with some bad bugs caused by memory leaks I ended up with this
simple solution: I made one header file that, when included, replaces
the malloc/calloc/realloc/free functions with some other functions
that do the actual job and insert (or remove) the pointers to the
allocated memory in a list.
Well ... if all you want to do is see if you are leaking, you could
just keep a count of the size of each allocation in a hidden header,
and just add and subtract as necessary and then print the last total
in an atexit() routine or something like that.
This is the real problem with the C standard, is that you can do a
*LOT* more than that, that is really useful. You can use special
patterns guards to detect bounds corruptions and also keep minimum and
maximum address bounds (on architectures where that makes sense) to
quickly detect garbage pointers being freed or realloced. The C
standard could expose functions like
int isLiveAllocation (const void *ptr);
size_t totalAllocatedMem (void);
size_t memSize (const void * ptr);
int walkEachAllocation (int (* walker) (const void * ptr, size_t
sz, void * ctx), void * ctx);
which are all fairly easily implementable with any serious dynamic
memory architecture. The value of such functions is so obvious,
especially in light of what you are trying to do.
There are also special problems that you need to deal with on some
platforms. strdup() makes direct system calls to malloc. So if you
try to free it with an overloaded free() macro, then you may encounter
problems. Basically, you need to redefine and make your own strdup as
well. You don't need to do this with functions like fopen, of course,
since they provide their own clean up (i.e., fclose).
[...] This way I can catch many errors as double
frees, allocations of zero size, or missing calls to free.
Obviously it works only when a given #define is set (NDEBUG, in my
case).
What do you think about it?
Thank you in advance!
(If you want to see the code I wrote a little page here:
http://technicalinsanity.org/out/simplegc/index.html)
Its fine as a first pass kind of thing.
The main problem, of course, is the free is horrendously slow for
large numbers of outstanding allocations. There is a common trick of
making the header appear at the address: ((char*)ptr) - sizeof
(header) which allows you to work around this performance problem.
The idea is that a pointer range check, alignment and header signature
check are very highly probabilistically good enough to detect a purely
bogus pointer. Its important to support programs that have a massive
number of memory allocations outstanding because that's where you will
get primary value from such a debugging mechanism.
And of course, this is pretty useless in multithreaded environments.
You need to make some sort of abstraction for a mutex or lock for your
memory (if you care about portability, otherwise you can just go ahead
and use the platform's specific mutexes). You can be clever and use a
hash on the value of ptr to create an array of striped locks to
increase parallelism (since malloc itself will be hit with a higher
degree of parallelism than you would otherwise encounter if you are
using a single lock), but that might be overkill.