Contact
Evil Genius SocietyHomeLearnForumLeaderboardSocialsMessages
← Back to module

Lesson 1 of 9

A C array is a fixed number of values laid out consecutively in memory:

#include <iostream>

int main() {
    int scores[5] = {90, 85, 77, 100, 68};

    std::cout << scores[0] << " " << scores[4] << "\n";

    scores[2] = 80;
    std::cout << scores[2] << "\n";

    std::cout << sizeof(scores) << " bytes\n";
    std::cout << sizeof(scores) / sizeof(scores[0]) << " elements\n";
    return 0;
}
90 68
80
20 bytes
5 elements

Indices run 0 to size - 1 — hence the loop conventions from lesson 4.3. The size must be a compile-time constant (lesson 3.7).

Initialization shortcuts

#include <iostream>

int main() {
    int a[3] = {1, 2, 3};
    int b[] = {1, 2, 3, 4};        // size deduced: 4
    int c[5] = {1, 2};             // rest zero-filled
    int d[4] = {};                 // all zero

    std::cout << sizeof(b) / sizeof(b[0]) << "\n";
    std::cout << c[0] << c[1] << c[2] << c[3] << c[4] << "\n";
    std::cout << d[0] << d[3] << "\n";
    return 0;
}
4
12000
00

Note int c[5] = {1, 2}; — supply fewer initializers than the size and the rest are value-initialized to zero. int d[4] = {}; zeroes everything, which is the idiomatic way to get a zeroed array. Without any initializer at all, the contents are garbage (lesson 3.1).

No bounds checking

This is the part that matters:

int scores[5] = {90, 85, 77, 100, 68};
scores[10] = 42;      // writes past the end — nothing stops you

C++ does not check. There's no exception, usually no crash — just a write into whatever memory happens to sit there. It might be another variable. It might corrupt something that fails ten minutes later in unrelated code.

This is undefined behaviour (lesson 8.7), and it's the single most notorious source of security vulnerabilities in C and C++. Module 11's sanitizers exist largely to catch it.

Decay: the array forgets its size

Here's the trap the lesson title promises. Pass an array to a function and it decays to a pointer — the size is lost:

#include <iostream>

void takes_array(int values[]) {
    std::cout << "inside:  " << sizeof(values) << "\n";
}

int main() {
    int numbers[5] = {1, 2, 3, 4, 5};
    std::cout << "outside: " << sizeof(numbers) << "\n";
    takes_array(numbers);
    return 0;
}
outside: 20
inside:  8

Outside, sizeof gives 20 — five ints. Inside, it gives 8: the size of a pointer, not the array. The parameter int values[] is a lie; the compiler silently rewrites it as int* values.

So sizeof(values) / sizeof(values[0]) — the idiom from earlier — computes 8/4 = 2 inside a function. Wrong, and silently so.

Happily, -Wall catches this exact mistake:

warning: 'sizeof' on array function parameter 'values' will return size of 'int*'

One more reason for the flags from lesson 2.4. But the warning only fires for this specific pattern — decay itself is silent everywhere else.

The consequence is that every function taking a C array must also take its length:

#include <iostream>

int sum(const int values[], std::size_t count) {
    int total = 0;
    for (std::size_t i = 0; i < count; ++i) {
        total += values[i];
    }
    return total;
}

int main() {
    int numbers[5] = {1, 2, 3, 4, 5};
    std::cout << sum(numbers, 5) << "\n";
    return 0;
}
15

That 5 is now your responsibility. Get it wrong — pass 6, or resize the array and forget to update the call — and you read past the end.

Multidimensional arrays

#include <iostream>

int main() {
    int grid[2][3] = {{1, 2, 3}, {4, 5, 6}};

    for (int row = 0; row < 2; ++row) {
        for (int col = 0; col < 3; ++col) {
            std::cout << grid[row][col] << " ";
        }
        std::cout << "\n";
    }
    return 0;
}
1 2 3 
4 5 6 

These are stored as one contiguous block, row after row.

So why learn them?

Honestly: because you'll read them. C arrays appear throughout existing C and C++ code, in every C API you call, and in embedded work where the alternatives aren't available.

For new code, don't use them. C++ gives you two better options that keep their size:

  • std::array (lesson 6.2) — a fixed-size array that knows how big it is.
  • std::vector (lesson 6.3) — a resizable one.

Both are exactly as fast for the same job, and neither decays silently.

Try it

Write a function int largest(const int values[], std::size_t count) returning the biggest element. Then call it with a deliberately wrong count and observe what happens — with -fsanitize=address if you can.

Checkpoint

  • A C array is a fixed, contiguous block; its size must be a compile-time constant.
  • int a[5] = {}; zero-initializes; leaving off the initializer leaves garbage.
  • There is no bounds checking — out-of-range access is undefined behaviour.
  • Passing an array to a function decays it to a pointer, losing the size, so sizeof inside the function is wrong.
  • Functions taking C arrays must take the length separately, and you must keep it correct.
  • Prefer std::array or std::vector in new code.

Questions about this lesson

Log in to ask a question.