Arrays

Course 1 · Ch 6
Arrays
The first genuine payoff of everything Chapter 1 promised about "manual control"

Every chapter so far has shown small toolchain and syntax differences from Rust. This one is different — arrays are where C's total absence of a safety net becomes something you can actually see happen.

Declaring & Initializing Arrays

int scores[5]; int values[5] = {10, 20, 30, 40, 50};

A plain C array has a fixed size, known at compile time — genuinely comparable to Rust's own fixed-size [T; N] array (not Vec, which can grow). Both languages have this exact concept. What happens when you go past the end is where they stop agreeing.

No Bounds Checking — The Real Difference

Accessing values[10] on a 5-element array is not an error in C. It's not a panic. It compiles, it runs, and it reads or writes whatever memory happens to sit past the array — memory that belongs to something else entirely. Rust's [T; N], by contrast, checks every index access at runtime and panics on an out-of-bounds index — a controlled, visible failure instead of silent memory corruption.

int values[5] = {10, 20, 30, 40, 50}; printf("%d\n", values[10]); // compiles and runs — prints garbage, or crashes, or "works"

A Concrete Demonstration

Two adjacent local variables often sit next to each other in memory. Writing past the end of one array can silently overwrite the other — no error, no warning, just a value that changed for no visible reason.

int small[3] = {1, 2, 3}; int sentinel = 42; small[5] = 0; // out of bounds — may silently corrupt sentinel printf("%d\n", sentinel); // might not print 42 anymore

Course 2's Memory Bugs chapter covers this class of problem in full, with real tooling (valgrind) to catch it. For now, the point is simpler: C genuinely will not stop this.

Array-to-Pointer Decay, Previewed

Chapter 5 established pass-by-value as C's default. Arrays quietly break that rule: passing an array to a function actually passes a pointer to its first element, not a copy of the whole array. Chapter 7 covers pointers in full — this is just naming the exception now, so it doesn't feel unexplained later.

Getting the Array's Size

sizeof(arr) / sizeof(arr[0]) computes the element count — but only where the array was actually declared. Once it decays to a pointer (inside a function it was passed to), sizeof on the parameter gives the pointer's size, not the array's.

int values[5] = {10, 20, 30, 40, 50}; int count = sizeof(values) / sizeof(values[0]); // 5 — correct, here
ConceptRust [T; N]C
Fixed size, known at compile timeyesyes
Out-of-bounds accesspanics — a controlled, visible failureundefined behavior — silent, no check at all
Passed to a functionby value (or reference, explicit) — stays a real arraydecays to a pointer to the first element
This is what the whole course keeps coming back to
Every future chapter's "manual control" comparison against Rust traces back to this one moment: C trusts the programmer to stay in bounds. Nothing in the language checks. Every safety habit from here forward exists because of this fact, not despite it.
sizeof gives the wrong answer inside a function
sizeof(arr) / sizeof(arr[0]) only works in the scope where the array itself was declared. Compute it there, and pass the count as a separate parameter — a genuinely common, real beginner trap otherwise.

Coding Challenges

Challenge 1

Declare an int array of 6 elements, initialize it with values, and print each element using a for loop and the sizeof(arr)/sizeof(arr[0]) trick to determine the loop bound.

📄 View solution
Challenge 2

Declare a 3-element array and a separate int variable directly after it. Write past the end of the array (e.g. arr[4] or arr[5]) and print the separate variable afterward. Explain what you observe and why C allows it.

📄 View solution
Challenge 3

Explain why sizeof(arr)/sizeof(arr[0]) gives the correct element count in the function where an array is declared, but gives a wrong answer if computed inside a different function the array was passed into.

📄 View solution

Chapter 6 Quick Reference

  • C arrays are fixed-size, known at compile time — genuinely comparable to Rust's [T; N]
  • No bounds checking, ever — an out-of-range access is undefined behavior, not an error or a panic
  • Rust's [T; N] checks every access and panics on out-of-bounds — the central contrast this chapter exists to demonstrate
  • Arrays passed to a function decay to a pointer to the first element — breaking Chapter 5's pass-by-value rule
  • sizeof(arr)/sizeof(arr[0]) only works where the array was declared — not after it decays to a pointer
  • Next chapter: pointers — the mechanism arrays already quietly depend on