Understanding Heap Memory Allocation in C - sbrk and brk
Tags: c, programming, tutorial, linux
Published: 2023-08-21 22:01:35

{% embed https://www.youtube.com/watch?v=NDKArv9WAlQ %}

In this tutorial, we will explore process memory to better understand how memory is allocated on the heap in C. To start, let's take a look at the general memory structure of a process.

## Stack Memory

When a function is called, the stack memory grows to accommodate the stack frame of the function. When the function returns, the stack memory shrinks in size as it pops off the stack frame of the function. The stack memory starts at the high end of memory and grows downwards, towards the heap.

![A system memory diagram](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/x0kg4vj2pcl9dkchckjg.png)

A special purpose register in your processor called a stack pointer tracks the current top of the stack. Each stack frame on the stack contains function arguments, local variables, and call linkage information such as return addresses for the function. Since functions can be called inside functions, we can often see multiple frames on a stack.

In C, we typically don’t need to worry about stack memory in too much detail. Knowing about how function frames work provides important context, but we don’t usually directly interact with the stack. When we allocate memory in C, we are interacting with the heap, which we will look at next.

## Heap Memory

The heap is a dynamic area of memory that grows towards the stack. When you allocate memory in a program, that memory is allocated on the heap. To allocate memory, we usually use **malloc**, which is based on another set of functions, **brk()** and **sbrk()**. These functions work by resizing the program break, which is the memory location that represents the end of heap memory and the start of unallocated memory. 

### brk and sbrk

Resizing the heap of a process is a simple task. We just need to tell the kernel to adjust the location of the program break (which is defined as the current top of the heap). Since the memory that lies passed the program break is unallocated memory, we can move the break into the unallocated memory to allocate some new memory for our process. There are two functions associated with this process:

* **brk(void *end_data_segment)**: Sets the program break to the location specified by *end_data_segment*. 
* **sbrk(intptr_t increment)**: Increments the size of the program break by *increment*.

To see how these functions work, let’s look at a simple example.

```c
#include <unistd.h>
#include <stdio.h>

int main(int argc, char *argv[]){

    int *currentBreak = sbrk(0);
    printf("%p\n",currentBreak);

}
```
In this example, provide an increment of 0 to sbrk. When an increment of 0 is provided to sbrk, the current break address is returned.


![The output of the first code snippet](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7p9mqzczik7dhz8cgr6r.png)

If you provide a value other than 0 to *sbrk*, you get the previous break address, meaning the address before the break was changed. We can see this effect using a few increments in a row.

```c
#include <unistd.h>
#include <stdio.h>

int main(int argc, char *argv[]){

    void *currentBreak = sbrk(0x5);
    printf("First increment of 0x5: %p\n",currentBreak);

    currentBreak = sbrk(0x5);
    printf("Second increment of 0x5: %p\n",currentBreak);

    currentBreak = sbrk(0x5);
    printf("Third increment of 0x5: %p\n",currentBreak);

    currentBreak = sbrk(0x5);
    printf("Fourth increment of 0x5: %p\n",currentBreak);

    currentBreak = sbrk(0x5);
    printf("Fifth increment of 0x5: %p\n",currentBreak);

}
```
When you run this set of increments, you will get a result similar to the following figure.


![The output of the second code snippet](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5qpnmr4docii5ho1tk24.png)

Notice that the break between the first and second increment is larger than the 0x5 we specified. This is because printf allocates memory to use as a buffer for stdout. When this happens initially, we see a large change in the heap break. The third, fourth, and fifth increment move consistently by the increment, 0x5, because no additional data is allocated on the heap between them. 

In most cases, we don’t use *sbrk* and *brk* directly to adjust the heap in C. Instead, we use a more user-friendly version of this process known as *malloc* and *free*. In the next article, we will explore these functions in more detail!