Memory in C Programming

Chiara Caprasi
7 min readMar 7, 2022
Memory layout in C programming language

One of the first thing that comes to mind when thinking about C programming language is memory allocation. In contrast to other programming language, C being very low level (that is close to the machine) allows the programmer to allocate memory dynamically. But first, let’s take a step back and look at what memory is.

What is memory?

When talking about memory in programming, we mostly refer to the RAM (Random Access Memory). As shown in the diagram above, the RAM is composed of different areas.

  • The stack is managed automatically by the compiler and is where local variable are stored. Stack moves downwards.
  • The heap is managed by the programmer and is where the dynamically allocated variables are stored in a C program. Heap moves upwards.

The stack and heap work in tandem — as the stack increases, the heap decreases and vice versa.

Even though for the rest of this article, I will be mainly referring to the stack and the heap, it’s worth looking at the other memory areas.

  • Static Data or BSS stores uninitialised variables. These variables are first initialised to zero and can then be modified. The data stored here exists throughout the entire length of the program, so they are global variables. This part is writable and not executable .
  • Initialised Data stores explicitly initialised variables such as string literals. This data never changes so it cannot be modified — it’s read-only and not executable.
  • Text — this is where functions and anything that makes the computers do something, like operators, loops, calculation (instructions) are stored in the memory. This part of the memory is read-only and executable.

Pointers

We can’t talk about memory without mentioning pointers!

Pointers are a crucial part of the C programming language — something quite unique to C. (As far as I know, C++ is the only other language that has pointers. Java and Python uses references that are similar to pointers but don’t work the same ways. Other more modern, more high level languages don’t have pointers)

It’s essential to understand that, in a typical computer memory architecture, each bytes of the memory has an address . When we declare a variable, the computer allocates some amount memory according to the variable data types. For example, on a modern 64-bit machine, this how much different data type occupies:

Char →1 byte (or 8 bits)

Int → 4 bytes (or 32 bits)

Float → 4 bytes (or 32 bits)

(You can always uses the sizeof(data_type) to check how much memory a type is occupying.)

So, why am I talking about bytes and addresses when we should talking about pointers? Because…

Pointers are variables that hold a memory address.

A pointer points to the value of another variable. They are indicated with * operator. For example, let’s take int* p. ‘p’ is a pointer to an integer and it holds the memory address where an integer value is stored. Note that the pointer p will also have an address — which will be different than the address of the int ‘p’ is pointing to.

Pointer takes 8 bytes of memory regardless of its type.

The syntax of pointers is as follow:

To declare pointers, as mentioned above we use * :

// data_type *pointer_name int *p; 

To store the address of a int in ‘p’, we use & before int variable name

int *p;
int = a;
p = &a;

To dereference a pointer (that is to modify the value that is store at the address the pointer points to) we use *p = new_value.

int *p; 
int a = 5;
*p = 8;

In the example above we have dereferenced p and assigning it a new value of 8 — which means that now a = 8 as well;

To recap:

p → is the address of the variable p is pointing to.

*p → is the value at the address that is stored in p.

One quick note on pointer arithmetic.

We use use:

p+1 we are incrementing the address stored at p.

*(p + 1) is incrementing the value stored at the address in p.

Important to keep in mind that this kind of operation can lead to segmentation fault — with pointer arithmetic it’s easy to involuntarily access spaces in memory we might not have access to. Hence, seg fault!

Now we understand the basics of pointers we can see what role they play in dynamic memory allocation .

Static/Automatic vs Dynamic Memory

Memory allocation in C can happen in two different ways — statically or dynamically.

As mentioned earlier, when we declare variables the computer allocates some amount of memory space based on the variable type. This process is called automatic or static memory allocation. In this case, program is taking care of the memory allocation. It reserves the space it needs and uses it accordingly — we, the programmer, don’t need to do anything extra. This memory is allocated in the stack (think sta-ck for sta-tic memory) during the compilation process.

However, sometimes when we declare a variable we don’t know how much space that variable will need. This is particularly common, for example, when we need to store a user answer. We don’t know exactly how long the user’s answer will be. For this we need to use dynamic memory allocation. In dynamic memory allocation the computer allocates memory in the heap at runtime, therefore it’s more flexible. We can allocate some memory and change it afterwards. Depending on the needs. we ca reduce or increase the memory space reserved.

To accomplish all of this we we have several library functions available.

Malloc, Calloc, Realloc…

Malloc, calloc and realloc allows the program to reserve memory space — they all allocate memory in the heap area.

Malloc allocates a certain amount of memory - in byte size — from the heap and return the address of the first byte of the memory block it allocates (if the request for memory is granted). The function prototype is as follow:

void *malloc(size_t size);

Malloc takes one argument — the size of the memory it needs to allocate — size is number of bytes and size_t is using the sizeof operator so we are sure to get the right amount of memory.

Calloc does the same as malloc but the only difference it initialise the memory space with 0.

void *calloc(size_t num, size_t size);

Realloc uses

void *realloc(void *ptr, size_t size);

… and Free!

As one of my favourite super-hero said, “With great power, comes great responsibility” . And that is the case of C.

C is a language of great performance as it allows to use memory efficiently, by allocate it dynamically. But, we have a responsibility that comes with this power.

The memory that is allocated with malloc, calloc or reallocis not automatically released when the function returns (in contrast to static memory allocation which automatically does that.) So, every time we allocate some memory dynamically we must free it.

Free is the function we would use for this task. This function’s syntax is:

void free(void *ptr);

A little something extra…

If you would like to understand calloc bette you might want to try and write your own calloc functions! This way it makes it easier to understand how it works under the hood.

Below is my own calloc function — if you are a CS / SE student please give it a go and compare them with mine to see how different or similar are our approaches.

My own calloc function

void *_calloc(unsigned int nmemb, unsigned int size) 
{
void *ptr;
char *p;unsigned int i;ptr = malloc(nmemb * size);if (ptr == NULL){ return (NULL);}i = 0;p = ptr;while (i < nmemb * size) { p[i] = 0; i++;}return (ptr);}

This is a helpful exercise as it shows clearly how alloc works — it reserves the memory size and then fills it with 0.

I hope that this article was useful in giving you a basic overview of how memory works in C — if you have any questions or would like to know more about any particular aspect/topic of the C programming language, please leave a comment by replying to this blog or ping me on LinkedIn at https://www.linkedin.com/in/chiara-caprasi.
If you would like to show your support please follow me on Medium, I would greatly appreciated that.

Stay happy and keep coding! 👩🏻‍💻

Additional Resources & References

On Memory

On Pointers

--

--

Chiara Caprasi

Women in Tech Advocate. Developer. Passionate about using technology to make a positive change.