~/articles/c-array-out-of-bound

In this course I noticed that the C programming language doesn't check for array out of bound errors, so I wanted to see for myself what would happen if I intentionally try to do so.

Demonstration

#include <stddef.h>
#include <stdint.h>
#include <stdio.h>

const int SIZE = 3;

int main()
{

    char    before[] = "test";
    uint8_t after[SIZE];
    // Char and uint8_t are essentially the same thing typed differently,
    // as characters are actually uint8 under the hood
    // This would create valid characters for visualization

    // Print each memory address of the array `after` to console
    printf("after \n");
    for (size_t i = 0; i < SIZE; i++) {
        printf("index is at: %ld\n", i);
        printf("mem. addr. : %p\n", &after[i]);
    }

    // Print the size and address of the `char[]` `before` to console
    printf("before \n");
    printf("mem. addr. : %p\n", &before);
    printf("size : %d\n", sizeof(before));

    // Before writing overbounds
    printf("%s\n", before);
    // This writes to the first char of the string
    after[SIZE] = 37;
    // After writing overbounds
    printf("%s\n", before);

    return 0;
}

This snippet of code would do this:

$ gcc main.c && ./a.out
main.c:30:5: warning: array index 3 is past the end of the array (which contains 3 elements) [-Warray-bounds]
    after[SIZE] = 37;
    ^     ~~~~
main.c:11:5: note: array 'after' declared here
    uint8_t after[SIZE];
    ^
1 warning generated.
after
index is at: 0
mem. addr. : 16FC5CB91
index is at: 1
mem. addr. : 16FC5CB92
index is at: 2
mem. addr. : 16FC5CB93
before
mem. addr. : 16FC5CB94
size : 5
test
%est

Note that gcc1 is warning me that I'm going over the bounds of an array. This is only a warning and the code still compiles. Yikes.

We can see that the last memory address allocated to the latter created variable after is right before the beginning of the memory address of the first created variable before.

This is coherent with the C programming language's memory model.

As we can see, the variables of fixed size are stored in the "stack". Stack is piled in a top-down fashion. As we can see with the array example, arrays in the stack are stored with the end of the array on top (upper addresses).

This is how the variable arrangement looks like in the stack: Note that before is a char[] of length of five (+1 for the ending null character)

before[4]
before[3]
before[2]
before[1]
before[0]
after[2]
after[1]
after[0]

In the snippet of code above, writing into after[SIZE], is equivalent to writing into after[3], which is equivalent to writing into the first character of before. We can tell that "test" has been overwritten as at the end of execution it becomes "%est", as 37 is "%" in ascii. Changing the number 37 will result in different characters.

1

It's actually clang under the hood, I'm an  sheep. (if you can't see that, it's an Apple logo)

<newer   earlier>