The malloc(3) API

Perhaps one of the most common APIs used by application developers is the renowned malloc(3).

The foo(3) syntax indicates that the foo function is in section 3 of the manual (the man pages)  a library API, not a system call. We recommend you develop the habit of reading the man pages. The man pages are available online, and you can find them at https://linux.die.net/man/.

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.

To print a variable of the size_t type within a printf, use the %zu format specifier:
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.

You will come across, shall we say optimists, who say things such as, "Don't bother checking malloc for failure, it never fails". Well, take that sage advice with a grain of salt. While it's true that malloc would rarely fail, the fact is (as you shall see), it could fail. Writing defensive code – code that checks for the failure case immediately – is a cornerstone of writing solid, robust programs.

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).

malloc(3) always returns a memory region that is aligned on an 8-byte boundary. Need larger alignment values? Use the posix_memalign(3) API. Deallocate its memory as usual with free(3).
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.
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset