Antonin Hérault
Antonin Hérault

Antonin Hérault

Pointers, references and memory allocation (7)

Antonin Hérault's photo
Antonin Hérault
·Jul 12, 2022·

6 min read

Subscribe to my newsletter and never miss my upcoming articles

Play this article

Table of contents

This tutorial might be hard to understand, don't hesitate to review any part one more time. Pointers scare a lot of programmers so I'm trying to make things simple here.

A pointer is the memory address of a variable, it redirects to the memory place where a value is stored.

Common point between arrays and pointers

When we create an array, we simply create a pointer to the beginning memory place.

int x = 5;
char hello[5] = "Hello";
int y = 10;

This is a dumb overview of the memory, and how are stored the variables :

________________________________________
| 5 | 'H' | 'e' | 'l' | 'l' | 'o' | 10 |
¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
 ⇈    ⇈                            ⇈
 x   hello                          y

Pointers and references

This is the syntax to declare a pointer :

<type>* <identifier> = &<variable identifier>;

To retrieve the pointed data from a pointer :

*<identifier>

To get the memory address of a variable, also called "reference" :

&<variable identifier>

Example

#include <stdio.h>
#include <assert.h>

int main(void) {
    int value = 5;
    int* ptr_value = &value;
    assert(*ptr_value == 5);

    return 0;
}

Using pointers in function parameters

Pointers are useful to save memory usage. Instead of creating a new variable and return it, we can use pointers in function parameters and directly modify a value from its pointer

#include <assert.h>

void triple(int* value) {
    *value *= 3;
}

int main(void) {
    int x = 5;
    triple(&x);
    assert(x == 15);

    return 0;
}

In this example, only one variable was created in the whole program.

But when we don't use pointers :

#include <assert.h>

int triple(int value) {
    value *= 3;
    return value;
}

int main(void) {
    int x = 5;
    x = triple(x);
    assert(x == 15);

    return 0;
}

The value parameter is a new variable inside the triple function. So, two variables were created there.

When you have the possibility to use pointers instead of simple parameters, use pointers.

Note that you can use a different syntax in parameters for pointers

void triple(int* value);
void triple(int value[]);

Got it ? Pointers and arrays are both the same. It's like having an array of an unknown length

Allocation functions

If you try to create an array with the pointer syntax, you will get important compiler warnings because it doesn't work.

But you told me that it was the same thing ?

Yes, but no. There are differences between the [length] and */[] syntaxes.

int values[5] = {9, 4, 1, 23, 6};

In this example, the compiler knows the memory size of values and can reserve space for the values.

But, if you try to create an array with the pointer syntax (with * or []), it will not work.

int* values = {9, 4, 1, 23, 6};
int values[] = {9, 4, 1, 23, 6};

The compiler cannot calculate the memory size because it doesn't have the array length, so you cannot assign the values correctly. You will get weird results.

To do this, we have to call a special function coming from the standard library.

The malloc function

This function permits to reserve some space in memory, and it returns the array beginning memory address.

Prototype for malloc :

void* malloc(size_t s);

What's size_t ? And how a pointer can be for void type ?

It's close to a long type, it's often used for memory size. When we don't know what type of value has to be pointed, we write void that stands for "unknown".

#include <assert.h>
#include <stdlib.h>

int main(void) {
    int* values = malloc(sizeof(int) * 5);
    assert(values != NULL);

    return 0;
}

We reserve 5 times the size for an integer value in the memory.

If the memory allocation has failed, the returned value is NULL, you have to check the validity of the pointer before using it.

Now, we can set values for the array

values[0] = 2;
assert(values[0] == 2);

The free function

When you allocate some a memory block, you have to free the allocated block. Welcome in C memory management ! In high-level languages, the compiler is smart, so it knows when an allocated block has to be freed. But with C, you have to manage it by yourself in your code.

void free(void* pointer);
#include <assert.h>
#include <stdlib.h>

int main(void) {
    int* values = malloc(sizeof(int) * 5);
    assert(values != NULL);

    values[2] = 10;
    assert(values[2] == 10);

    free(values);
    assert(values[2] != 10);

    return 0;
}

After calling free for a pointer, you cannot use it again (unless you want to have errors ?!)

The realloc function

It attempts to resize the pointed memory block, previously allocated by malloc

void* realloc(void *pointer, size_t s);

Other memory functions

There are some more memory functions, but we will see that in a post talking about strings because they have a great role in this subject. Strings are just arrays with big memory management

Common errors and debugging tools

If you have already played with the arrays and the memory allocation functions, you probably got a terrible error named "Segmentation fault" or "stack smashing detected", well something with "... (core dumped)".

With C, and because you have to manage the memory by yourself, you may often see this sort of errors.

There is a tool called "valgrind". It's an awesome tool to see what happens in memory during your program run.

#include <stdlib.h>

void foo(void) {
    int* x = malloc(10 * sizeof(int));
    x[10] = 0;        
}

int main(void) {
    foo();
    return 0;
}
gcc main.c -o program
valgrind ./program
  ==19182== Invalid write of size 4
  ==19182==    at 0x804838F: foo (main.c:6)
  ==19182==    by 0x80483AB: main (main.c:11)
  ==19182==  Address 0x1BA45050 is 0 bytes after a block of size 40 alloc'd
  ==19182==    at 0x1B8FF5CD: malloc (vg_replace_malloc.c:130)
  ==19182==    by 0x8048385: foo (main.c:5)
  ==19182==    by 0x80483AB: main (main.c:11)
...
  ==19182== 40 bytes in 1 blocks are definitely lost in loss record 1 of 1

The log is much more complete. You can see memory problems and know if you have freed all the allocated blocks.

Note

int values[3] = {1, 2, 3};
assert(*values == 1);
assert(values[0] == 1);

As we saw, an array is just a pointer to the beginning memory place for the values. So, by "getting the value" from a pointer with *<identifier>, it's doing the same than getting the first element.


Exercises

  1. Fix the following code :

     #include <stdio.h>
    
     int main(void) {
         int* values = {1, 2};
         printf("%i\n", values[0]);
    
         return 0;
     }
    

Solutions

  1. Fixing code

     #include <stdio.h>
     #include <stdlib.h>
    
     int main(void) {
         int* values = malloc(sizeof(int) * 2);
         values[0] = 1;
         values[1] = 2;
         printf("%i\n", values[0]);
         free(values);    
    
         return 0;
     }