3 Aug '24
I first encountered the C programming language during my undergraduate days as part of my introductory course to computer science. Back then, it was a challenge for me as programming was new to me, with no prior knowledge whatsoever. Address, pointers and memory allocations were enough to throw me off programming, wondering if I will ever make it in programming. I recently revisited C in order to build more breadth in my programming skills. I have been using Java in my new role and I thought that it would be nice to supplement my skills with some C/C++ knowledge.
C was created in the 1970s by Dennis Ritchie. It is an imperative procedural language with uses in operating systems and device drivers. C does not have object-orientation and garbage collection. Many languages have been built on the ideas of C, such as C++ and Java. Thus C's syntax is strikingly similar to Java.
Coming back to me revisiting the language, I dived straight into the topics of pointers, address and memory management as these are not taught in Java. Java handles them under the hood by abstracting these details from the programmer, providing a safer and more managed environment. Nonetheless, having knowledge of these topics would enable us to develop a greater appreciation at how computers manage memory and data. In some cases, there are some performance-critical applications which require fine-grained control of memory, which can be achieved with C. Let's discuss these topics briefly. Pointers and addresses will be discussed together since their usage in C language are closely related to each other. Memory management will have its own topic coverage below.
Pointers and Address
Pointers are variables that store the memory address of another variable. There are a few ways to declare a pointer.
a) int* p; b) int *p; c) int * p;
All 3 approaches will work fine and it depends on individual preference. Personally, I prefer approach a). Visually, it tells me that p is a pointer which points at a memory that stores an integer. However, it might be a source of confusion when it is declared like this;
int* p, q;
In the above example, although p is a pointer variable which stores an integer, q is not. It is an integer variable. For this reason, some prefer the style below to avoid confusion when declaring multiple variables in the same statement.
int *p, q;
Next, let's assign an address to a pointer. Recall that a pointer is a variable that store the memory address of another variable. Here, pointer b points to/stores the memory address of integer c which is assigned a value of 3. Note that the memory address is not the same as the integer value.
int* b, c; c = 3; b = &c;
Below shows how the addresses of int c and pointer b are obtained. The address operator, '&' is used to obtain the address of int c. Notice that the address operator is not used to retrieve the address value of pointer b as b already stores the address value.
printf("Address of c: %p\n", &c); printf("Address of pointer b: %p\n", b);
To retrieve the value pointed by b, we use the dereferencing operator, *, as shown below.
printf("Value of pointer b: %d\n", *b);
Memory management
Memory management is the process of handling how much memory a program uses through allocation, reallocation and deallocation (w3schools.com/c). There are two types of memory; static and dynamic memory. Static memory, also known as compile-time memory, is memory that is reserved for variables before the program runs. Static variable is created through the declaration of variables.
Dynamic memory, also known as runtime memory, is memory that is allocated after the program starts running. Codes can be written to manage memory for allocation, reallocation and deallocation.
int *ptr1, *ptr2; ptr1 = malloc(sizeof(*ptr1)); ptr2 = calloc(3, sizeof(*ptr2));
In the example above, two integer pointers are declared. ptr1 and ptr2 are allocated memories dynamically using malloc and calloc. malloc and calloc are accessed from the standard library, 'stdlib.h'. malloc( ) accepts one parameter, size, which specifies how much memory to allocate, measured in bytes. calloc( ) accepts two parameters; amount of items and size. It is suited for situations involving an array or a block of elements.
When either malloc or calloc are called, the system's memory manager finds a suitable block of free memory of at least the requested size. In this case, ptr1 points to 4 bytes of allocated memory while ptr2 points to 12 bytes of allocated memory.
// Allocate memory int* ptr; ptr = calloc(5, sizeof(*ptr)); // Write to the memory *ptr = 14; ptr[1] = 4; ptr[2] = 6; // Read from the memory printf("%d %d %d %d %d", *ptr, ptr[1], ptr[2], ptr[3], ptr[4]); printf("%d %d %d %d %d", *ptr, *(ptr+1), *(ptr+2), *(ptr+3), *(ptr+4));
The above example show hows an integer pointer, 'ptr', points to an array of size 5. calloc allocates memory for an array of 5 int elements and initialises all elements to zero. The dereferencing operator *, can be used to access the first element, assigning a value to it. The second print statement uses pointer arithmetic syntax. The output for both are
14 4 6 0 0
Other memory management functions are realloc( ) and free( ). If the inital amount of memory allocated is not enough, it can be reallocated with a larger amount of memory using realloc( ) while keeping the data that was stored in it.
ptr2 = realloc(ptr1, new_size);
When a block of memory is no longer needed, it should be deallocated / freed up. Memory leaks can happen when dynamic memory is allocated but never freed. So, if a function is with dynamic memory allocation is called often, it will consume too much memory until the system slows down. To free up the memory allocated to the pointer, simply use the free () function.
free(ptr_name);
This concludes the article's discussion on pointers, addresses and memory management in C. While not exhaustive, it provides a solid foundation for further exploration of these concepts.