Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C

Allocated Memory Management in C

4.31/5 (5 votes)
14 Apr 2016CPOL5 min read 13.1K  
Handle the dynamic allocated

Introduction

When you program in C, one of the bigger pain is the handling of the dynamic allocated memory. You have to call malloc() for each memory allocated. Bust most important, you have to free() it when the variable is not more required. A lot of bugs come out from the bad handling of these instructions. The best case is that you just forgot to free() it. But sometimes, the bug is hide. For example, you overwrite a variable containing a pointer, and the pointer gets lost. In C, this influences also the architecture of the program. For example, in my last article http://www.codeproject.com/Tips/1091150/Fast-Graph-Traversal, I supposed the existence of an array containing all the nodes and an array containing all the edges. This architectural solution, useless in some other languages, is mandatory in C. There is no way to free() a graph, because for definition there is no root. With my algorithm, maybe you can decompose it in a tree, but at the same, you write another program just to free() the graph.

Pre-processor

C gives a useful instrument called directives of the pre-processor. If you want to know what a preprocessor does, I suppose you are a beginner. So I'm not going to give the academic definition, but quite an understandable definition.

The directives of the pre-processor are an instrument intended to help the programmer to write code, with side effect to improve the performances of your program. So if helps to write code, it means that works in the textual human readable code. And it does exactly this, it replaces some text with another one. It dumbly compares strings and replaces them. Why improve your performance? Because you can also declare some "functions", therefore are called macros and instead to use a less efficient call of function, it simply replaces the macro with your instructions. Traditionally, the functions getc() and putc() are macros because instead of calling a function for each character in a text, it does the evaluation locally.

So we told that these are an instrument for writing code, for helping the programmer, but the scope of this article is exactly this. Help the programmer to handle the variables and detect some mistakes before the release. Better when you release it, is better you remove all these directives.

Which directives are we going to use?

#define abs(foo) (foo)>=0?(foo):(-foo) this is just an example. What does this macro do? It just replaces each occurrence of abs() with the relative instruction (foo)>=0?(foo):(-foo) notice that there is no semi-colon. Because it just replaces the text, if you put the semi-colon, it is going to also add the semi-colon in your code, but probably it is not required.

__LINE__ this is a standard ANSI macro, it replaces the word __LINE__ with the relative line number.

__FILE__ is also standard, it replaces the word __FILE__ with the relative name of the file (Example: FOO.h, or main.h)

#undef abs(foo) stops the definition of the macro defined. So from this point, your macro is not going to be replaced more.

Sometimes, I have just the impression that there is a war against the pre-processor directives. The C++ purists proudly say that these directives are going to be redundant. Each time I listen to such comments, I ask myself "what is wrong with you?". Why make a war against an instrument? Why make a war against an instrument intended to help you? Because this is the scope of the macros: help the programmer. What may be C++ wants is to give an alternative to the sides effect of the macros: improve the performance. What are we going to do, is not possible to do with the inline functions or constant. We need an instrument that dumbly replaces text. But it is also not true that the C++ inline functions are redundant. If I have a big function, I am quite uncomfortable writing with macros. For example: the function next_row() of the article fast graph traversal, I would declare it online. It gets repeated n times, but for improving the readability, I choose to write outside the main function, and it is too big to do it with a macro.

So each instrument has his scope of work. What I really do not understand is const - this is absolutely unnecessary.

Using the Code

We assume you have the routines for a hashtable declared. If you don't ... not so good. You must search the code and write one.

So what is the scope: we readdress all the calls of malloc to our custom function, where we effectively allocated the memory, but also we add the name of the address of the new allocated pointer, with relative file and line tags. So we know at each moment which variables have allocated. And if at each call of free(), we pop() the same variables, when we call the escape routine, nobody should be allocated. But if somebody is there, we know who and where get allocated. And reduce your debug to a local study.

First of all, readdress malloc() (similarly, you must do it for each function that handles memory: strdup(), calloc(), realloc(), in this last case, you have to pop the current pointer and add the new).

C++
#define malloc(a) smalloc((a),__FILE__,__LINE__)

This replaces all your occurrences of malloc, with your custom function smalloc(), that accepts three parameters without touching a single row of your code. When you remove this macro, before the release, all returns to normal.

We do the same also with free():

C++
#define free(a) sfree((a),__FILE__,__LINE__)

Ok now we have to write our functions, smalloc() and sfree(), but before do it, we must translate the address of pointer in an human redeable string. Or it is not possible to compute a hash on an address. To do this, we use the property of the unions. These allocate two or more variables in the same "slot" of memory. And if you put a variable, and aced with the other, you know how it is going to appear if it had another datatype... (quite close with definition).

C++
union pointer_translator
{
	void * pointer;
	unsigned value; //if int or long depends from the architecture.
}

We build our struct to push in the hashtable:a.

C++
struct file_line 
{
 	char * file;
	unsigned line;
 } 

and opla! we write the functions:

C++
//first we undefine the macros locally, or we are going to have a infinite loop:
#undef malloc(a)
#undef free(a)
void *smalloc(size_t sz, char *fl, unsigned l)
{
	char st[10];
	void *res=malloc(sz);
	struct pointer_translator pt;
	pt.pointer=res;
	atoi(st,pt.value,10);
	struct file_line* f_l=(struct file_line*)malloc(sizeof(struct file_line));	
	f_l->file=strdup(fl);
	f_l->line=l;	
	hadd(hshtbl, st,f_l);
	return res; 
}
//similarly for free()
void sfree(void * p, char *fl, unsigned l)
{
	char st[10];
	struct file_line *fl;
	struct pointer_translator pt;
	pt.pointer=p;
	atoi(st,pt.value,10);
	fl=(struct file_line *)hpop(hshtbl,st);
	if (!fl)	
		printf("%s %d\n",fl,l);
	destroy_fl(fl);	
	free(p);

}
//now we redifine the macros
#define malloc(a) smalloc((a),__FILE__,__LINE__) 
#define free(a) sfree((a),__FILE__,__LINE__) 

Remember to undefine the macros also inside the code of hashtables.

That is all folks... good C to all.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)