2011/03/15

Memory allocation and argument passing in C for noobies like myself

Hey,

The other day a friend of mine asked me a C question, to see if I knew the answer. It was a long time since I wrote any C code (since Campus mostly) and I completely forgot the details about memory allocation and argument passing in C.

So here it goes, he asked what is the output of the following code:

#include 
#include

void initialize(char* buffer)
{
buffer = malloc(sizeof(char));
*buffer='a';
}

int main(void) {

char* buff;
buff = malloc(sizeof(char));
*buff = 'b';
initialize(buff);
puts("Contents of buffer are: ");
printf("%c\n", *buff);
return 0;
}

At first glance, specially if you haven't programmed C for a long time (like me), you may think that the console output should be 'a' instead of 'b' right? Well, I recommend that you try the above code and see the results. After all, we are passing a pointer to initialize(), so we should be able to modify the contents of char pointed ...Well, no.

Passing a pointer in C is like passing by value, thus, a copy of the variable is made. This is why we cannot modify the contents of the original buffer declared in main. Besides, in the above code we are reserving memory in main():


buff = malloc(sizeof(char));



And reserving memory again in initialize():



buffer = malloc(sizeof(char));

Thus, when initialize is called and malloc called again, we have completely lost access to the memory space allocated in main, hence causing a memory leak that can degrade performance.

A possible solution could be to remove memory allocation from initialize():




void initialize(char* buffer)
{
*buffer='a';
}

This will print 'a' at the end, because, although a copy of the pointer is created when initilize() is called, *buffer is pointing to the memory space allocated in main. Keep in mind that we have to pointers now, the one declared in main and the one created when initialize was called, which could be leading to some problems too.

Another solution (the one I like the most) would be to not create a copy when calling initialize. Now the question is how you do this in C without references like in C++? Answer comes in the form of a pointer to a pointer (**). Redeclaring initialize so it receives a pointer to a pointer to char and adding value accordingly:


void initialize(char** buffer)
{
*buffer='a';
}

A pointer to a pointer behaves like a C++ reference, thus, we can modify the contents of the passed variable, a char pointer in this case. In order to do so, when calling initialize we need to pass the memory address of the variable (&). If we rewrite main() like this:



int main(void) {
char* buff;
buff = malloc(sizeof(char));
*buff = 'b';
initialize(&buff);
puts("Contents of buffer are: ");
printf("%c\n", *buff);
return 0;
}

Calling initialize passing the variable with & is like passing the variable by reference. Then we can modify the value and contents of the variable with no problems. We could do some evil, changing where it points to a non-allocated memory address to get a nice segmentation fault :).

Anyway, nice refreshment about C topics, hope you like it. Here is the final version of the code:


#include
#include

void initialize(char** buffer)
{
*buffer='a';
}

int main(void) {
char* buff;
buff = malloc(sizeof(char));
*buff = 'b';
initialize(&buff);
puts("Contents of buffer are: ");
printf("%c\n", *buff);
return 0;
}

References:

  • This is a topic I created in StackOverflow about this issue:

http://stackoverflow.com/questions/5309245/c-char-question

  • Just for fun:

the sound of C








No comments:

Post a Comment