Many Values, One Name

Every variable so far has held exactly one value, so twenty marks meant twenty declarations. An array fixes that: one name for a numbered run of elements, all the same type, sitting side by side in memory. Together with last chapter's for loop, whose i = 0; i < n; i++ shape was built for exactly this, arrays are where programs start handling data rather than a value or two.

Declaring and Indexing

#define MAX_MARKS 5

int marks[MAX_MARKS];

The declaration reserves five ints. In ANSI C the size must be a compile-time constant, which is why it comes from a #define (chapter 2's rule that a const variable does not qualify becomes concrete here; variable-length arrays are a C99 feature and an error in this dialect).

Elements are numbered from 0: marks[0] is the first, marks[4] the last. An array of n elements has valid indexes 0 through n-1, and the off-by-one this invites is the most manufactured bug in C teaching for good reason.

The i < COUNT test visits exactly 0 through 4: the loop shape and the index range are the same fact.

Initialization

A brace list initializes elements in order, and two rules do real work. Extra elements, when the list is shorter than the array, are zeroed; and the list may set the size implicitly:

int primes[5] = {2, 3, 5, 7, 11};   /* all five given            */
int partial[5] = {1, 2};            /* rest become 0             */
int zeros[5] = {0};                 /* the all-zero idiom        */
int sized[] = {10, 20, 30};         /* size 3, counted for you   */

Without any initializer, an array of automatic storage holds indeterminate values, the same rule as scalar variables, chapter 2's zero-for-static exception included. The = {0} idiom is how course code starts an array clean.

Too few initializers is a feature, then. Too many is a mistake the language refuses to guess at:

int number[3] = {10, 20, 30, 40};
error: excess elements in array initializer
note: (near initialization for 'number')

The asymmetry is worth holding onto. A short list has an obvious reading, so the standard defines one: zero the rest. An over-long list has no sensible reading at all, and the standard makes it a violation the compiler must report. Note where the complaint happens: at compile time, before the program exists. Nothing is written past the end of the array and no undefined behaviour occurs, which makes this the one array mistake in the chapter that cannot reach a running program. A lenient compiler is allowed to report it and carry on, and gcc without this course's -pedantic-errors setting downgrades exactly this message to a warning and discards the extra value; old exam papers state flatly that it "is illegal in C", and that is what they mean.

Memory: Contiguous, and Why It Matters

An array's elements are adjacent in memory: marks[1] starts exactly sizeof(int) bytes after marks[0], with no gaps. marks[i] is therefore pure arithmetic, start plus i times the element size, which is why indexing is fast, why the sizes from chapter 2 matter, and why sizeof(marks) inside the declaring function is the whole array: 20 bytes for five 4-byte ints, giving the element-count idiom sizeof(marks) / sizeof(marks[0]).

Out of Bounds: Undefined Behaviour

marks[5], one past the end, is not an error message; it is undefined behaviour. C never checks an index; the arithmetic lands wherever it lands, reading or overwriting whatever lives there. The standard promises nothing, and this platform's AddressSanitizer aborts the run when an exercise touches memory outside an array, which is a kindness: on machines without it, the same bug corrupts silently. The loop discipline that avoids the entire family: the bound in i < n is the same n as the declaration, and <= in that position is the classic mistake.

Reading Into an Array

The working pattern, guards included:

Three things earn their place. The count is validated against the array's capacity before any element is read, because accepting n greater than MAX_VALUES turns the read loop itself into out-of-bounds writes. Each element read is guarded. And the output loop counts down, printing the values reversed, the simplest taste of the reordering work arrays exist for.

Counting With a Computed Index

Every index so far came from a loop counter. The second great use of an array is to let the data choose the index: an array of counters, one per category, where each value tells you which counter to bump. Dividing a mark by 10 turns 0 to 100 into band numbers 0 to 10, and group[mark / 10]++ tallies it in one statement, no chain of ifs in sight.

  0-  9 1
 10- 19 1
 20- 29 1
 30- 39 0
 40- 49 1
 50- 59 2
 60- 69 3
 70- 79 1
 80- 89 1
 90- 99 0
100-100 1

Three things here are the lesson. = {0} matters more than usual: counters that start at indeterminate values produce confident nonsense, so the zeroing idiom is load-bearing rather than tidy. The size is 11, not 10, because a mark of 100 divides to band 10 and needs a slot of its own; that final band holds exactly one value, which is why its label reads 100-100.

And the range check is not input politeness, it is the bounds check. Once the index is computed from data, the data decides where the write lands: a mark of 150 would compute band 15 and write four elements past the end of group, undefined behaviour caused by nothing worse than a typo in a mark sheet. Validating the value is validating the index, and that equivalence is the habit to carry forward.

Key Takeaways

  • int a[N] declares N elements indexed 0 to N-1; ANSI C sizes come from #define constants, never variables.
  • Brace lists initialize in order, zero the remainder, and = {0} is the all-zero idiom; uninitialized automatic arrays hold indeterminate values.
  • Too few initializers is defined (zero fill); too many is a diagnosed constraint violation, an error under this course's settings, caught at compile time rather than at run time.
  • Elements are contiguous: indexing is arithmetic, sizeof(array) is the whole object, and sizeof(a)/sizeof(a[0]) counts elements.
  • Any index outside 0 to N-1 is undefined behaviour; C never checks, and ASan aborts where other machines corrupt silently.
  • Validate a count against capacity before reading elements; i < n up, i = n - 1; i >= 0 down are the canonical walks.
  • An array of counters indexed by a computed value (group[mark / 10]++) replaces a chain of ifs; when the data picks the index, validating the value is the bounds check.