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
sizeofinside the function is wrong. - Functions taking C arrays must take the length separately, and you must keep it correct.
- Prefer
std::arrayorstd::vectorin new code.
Questions about this lesson
Log in to ask a question.