using INT(3) to set break point at the code is my all time favorite
I liked the variable sized structures you could make:
typedef struct {
unsigned int size;
char buffer[1];
} tSizedBuffer;
tSizedBuffer *buff = (tSizedBuffer*)(malloc(sizeof(tSizedBuffer) + 99));
// can now refer to buff->buffer[0..99].
Also the offsetof macro which is now in ANSI C but was a piece of wizardry the first time I saw it. It basically uses the address-of operator (&) for a null pointer recast as a structure variable.
Steve Webb has pointed out the __LINE__ and __FILE__ macros. It reminds me of how in my previous job I had hacked them to have in-memory logging.
I was working on a device where there was no port available to pass logging information from device to the PC being used for debugging. One could use breakpoints to halt and know the state of the program using debugger but there was no information on system trace.
Since all calls to debug logs were effectively a single global macro, we changed that macro to dump file name and line number on to a global array. This array contained series of file names and line numbers showing which debug calls were invoked, giving a fair idea of execution trace (not the actual log message though). One could pause the execution by debugger, dump these bytes onto a local file and then map this information to the code base using scripts. This was made possible because we had strict coding guidelines, so we could make had to make changes to the logging mechanism in one file.
Wrap malloc and realloc like this:
#ifdef _DEBUG
#define mmalloc(bytes) malloc(bytes);printf("malloc: %d\t<%s@%d>\n", bytes, __FILE__, __LINE__);
#define mrealloc(pointer, bytes) realloc(pointer, bytes);printf("realloc: %d\t<%s@%d>\n", bytes, __FILE__, __LINE__);
#else //_DEBUG
#define mmalloc(bytes) malloc(bytes)
#define mrealloc(pointer, bytes) realloc(pointer, bytes)
In fact, here is my full arsenol (The BailIfNot is for OO c):
#ifdef _DEBUG
#define mmalloc(bytes) malloc(bytes);printf("malloc: %d\t<%s@%d>\n", bytes, __FILE__, __LINE__);
#define mrealloc(pointer, bytes) realloc(pointer, bytes);printf("realloc: %d\t<%s@%d>\n", bytes, __FILE__, __LINE__);
#define BAILIFNOT(Node, Check) if(Node->type != Check) return 0;
#define NULLCHECK(var) if(var == NULL) setError(__FILE__, __LINE__, "Null exception", " var ", FATAL);
#define ASSERT(n) if( ! ( n ) ) { printf("<ASSERT FAILURE@%s:%d>", __FILE__, __LINE__); fflush(0); __asm("int $0x3"); }
#define TRACE(n) printf("trace: %s <%s@%d>\n", n, __FILE__, __LINE__);fflush(0);
#else //_DEBUG
#define mmalloc(bytes) malloc(bytes)
#define mrealloc(pointer, bytes) realloc(pointer, bytes)
#define BAILIFNOT(Node, Check) {}
#define NULLCHECK(var) {}
#define ASSERT(n) {}
#define TRACE(n) {}
#endif //_DEBUG
Here is some example output:
malloc: 12 <hash.c@298>
trace: nodeCreate <hash.c@302>
malloc: 5 <hash.c@308>
malloc: 16 <hash.c@316>
malloc: 256 <hash.c@320>
trace: dataLoadHead <hash.c@441>
malloc: 270 <hash.c@463>
malloc: 262144 <hash.c@467>
trace: dataLoadRecursive <hash.c@404>