Perhaps one of the most common APIs used by application developers is the renowned malloc(3).
We use malloc(3) to dynamically allocate a chunk of memory at runtime. This is as opposed to static—or compile-time – memory-allocation where we make a statement, such as:
char buf[256];
In the preceding case, the memory has been statically allocated (at compile-time).
So, how exactly do you use malloc(3)? Let's check out its signature:
#include <stdlib.h>
void *malloc(size_t size);
The parameter to malloc(3) is the number of bytes to allocate. But what is the size_t data type? Obviously, it's not a C primitive data type; it's a typedef – long unsigned int on your typical 64-bit platform (the exact data type does vary with the platform; the important point is that it's always unsigned – it cannot be negative. On a 32-bit Linux, it will be unsigned int). Ensuring that your code precisely matches the function signature and data types is crucial in writing robust and correct programs. While we're at it, ensure that you include the header file that the man page displays with the API signature.
size_t sz = 4 * getpagesize();
[...]
printf("size = %zu bytes ", sz);
In this book, we will not delve into the internal implementation details regarding how malloc(3) and friends actually store, allocate, and free memory (refer the Further reading section on the GitHub repository.) Suffice to say, the internal implementation strives to be as efficient as can be; using these APIs is usually considered the right way to perform memory-management.
The return value is a pointer to the zeroth byte of the newly-allocated memory region on success, and NULL on failure.
So, using the API is very straightforward: as an example, allocate 256 bytes of memory dynamically, and store the pointer to that newly allocated region in the ptr variable:
void *ptr;
ptr = malloc(256);
As another typical example, the programmer needs to allocate memory for a data structure; let's call it struct sbar. You could do so like this:
struct sbar {
int a[10], b[10];
char buf[512];
} *psbar;
psbar = malloc(sizeof(struct sbar));
// initialize and work with it
[...]
free(psbar);
Hey, astute reader! What about checking the failure case? It's a key point, so we will rewrite the preceding code like so (and of course it would be the case for the malloc(256) code snippet too):
struct [...] *psbar;
sbar = malloc(sizeof(struct sbar));
if (!sbar) {
<... handle the error ...>
}
Let's use one of the powerful tracing tools ltrace to check that this works as expected; ltrace is used to display all library APIs in the process-execution path (similarly, use strace to trace all system calls). Let's assume that we compile the preceding code and the resulting binary executable file is called tst:
$ ltrace ./tst
malloc(592) = 0xd60260
free(0xd60260) = <void>
exit(0 <no return ...>
+++ exited (status 0) +++
$
We can clearly see malloc(3) (and the fact that the example structure we used took up 592 bytes on an x86_64), and its return value (following the = sign). The free API follows, and then it simply exits.
It's important to understand that the content of the memory chunk allocated by malloc(3) is considered to be random. Thus, it's the programmer's responsibility to initialize the memory before reading from it; if you fail to do so, it results in a bug called Uninitialized Memory Read (UMR) (more on this in the next chapter).
Details can be found on the man page at https://linux.die.net/man/3/posix_memalign.
Examples of using the posix_memalign(3) API can be found in the Locking memory and Memory protection sections.