Arrays

An array is a fixed number of objects of one type, stored next to each other. The index is an offset from the first element, and C checks it nowhere: reading or writing outside the array is undefined behaviour, not an error message. The size is part of the declaration and never changes afterwards.

Declaration and initialisation

The size goes in brackets, either as a constant or inferred from an initialiser. Missing initialisers become zero, which is the one place where a partial list is safe; an array with no initialiser at all inside a function starts indeterminate, like any automatic object from lesson 18.

#include <stdio.h> int main(void) { int scores[5] = {90, 75, 88}; char name[6] = "Ada"; double weights[3] = {1.5, 2.0}; printf("scores: %d %d %d %d %d\n", scores[0], scores[1], scores[2], scores[3], scores[4]); printf("name: %s\n", name); printf("weights: %.1f %.1f %.1f\n", weights[0], weights[1], weights[2]); printf("scores bytes: %zu\n", sizeof scores); return 0; }

gcc -Wall -Wextra declare.c -o declare
./declare

scores: 90 75 88 0 0
name: Ada
weights: 1.5 2.0 0.0
scores bytes: 20

Five int values take 20 bytes, and the last two scores are zero. The string has six bytes for three letters, a terminator and two spare zeros; lesson 23 explains why the extra byte is not optional.

sizeof and the element count

sizeof of an array is its total size in bytes, so dividing by the size of one element gives the count. The division has type size_t; the cast to int keeps the loop variable in a signed type, which matters for a countdown.

#include <stdio.h> int main(void) { int values[] = {4, 8, 15, 16, 23, 42}; int count = (int) (sizeof values / sizeof values[0]); int sum = 0; int largest = values[0]; for (int i = 0; i < count; i++) { sum += values[i]; if (values[i] > largest) { largest = values[i]; } } printf("count: %d\n", count); printf("sum: %d\n", sum); printf("largest: %d\n", largest); return 0; }

gcc -Wall -Wextra iterate.c -o iterate
./iterate

count: 6
sum: 108
largest: 42

The count expression only works while the array type is visible. Once the array is passed to a function it becomes a pointer to its first element and sizeof measures the pointer instead; the pointer lesson in this course takes that apart, and the workaround is to pass the count next to the array.

Iterating with a bound

Every loop over an array needs a bound, and the bound is the count, not the largest index. The last valid index is count - 1. Writing the condition as i < count keeps the two apart and makes an empty array harmless.

#include <stdio.h> int main(void) { int values[] = {3, 1, 4, 1, 5, 9, 2, 6}; int count = (int) (sizeof values / sizeof values[0]); for (int i = 0; i < count; i++) { printf("%d:%d ", i, values[i]); } printf("\n"); return 0; }

gcc -Wall -Wextra indexloop.c -o indexloop
./indexloop

0:3 1:1 2:4 3:1 4:5 5:9 6:2 7:6

The index and the value are printed together so the two lines of the output can be checked against the initialiser. The trailing space after the last pair is the separator the loop prints every time; a condition with i < count - 1 around the space would remove it only from this run.

Two-dimensional arrays

A two-dimensional array is an array of arrays: grid[row][col] is the element at the given row and column, and both indexes start at zero. The inner braces in the initialiser follow the rows, and the first index is the slower one to change.

#include <stdio.h> int main(void) { int grid[3][3] = { {1, 2, 3}, {4, 5, 6}, {7, 8, 9}, }; for (int row = 0; row < 3; row++) { int row_sum = 0; for (int col = 0; col < 3; col++) { printf("%3d", grid[row][col]); row_sum += grid[row][col]; } printf(" | %d\n", row_sum); } return 0; }

gcc -Wall -Wextra matrix.c -o matrix
./matrix

1 2 3 | 6
4 5 6 | 15
7 8 9 | 24

The trailing comma after the last row is legal and keeps a diff small when a row is added. The rows are contiguous in memory, so a two-dimensional array is a flat block of nine ints with a convenient index syntax.

Too many initialisers, out of bounds

The size in the declaration is a promise, and an initialiser with more values breaks it. GCC reports the extra element and still builds the program with the first three:

#include <stdio.h> int main(void) { int values[3] = {1, 2, 3, 4}; printf("%d\n", values[0]); return 0; }

gcc -Wall -Wextra badinit.c -o badinit

badinit.c: In function 'main':
badinit.c:5:31: warning: excess elements in array initializer
5 | int values[3] = {1, 2, 3, 4};
| ^
badinit.c:5:31: note: (near initialization for 'values')

The second mistake is an index equal to the size. It compiles, and only optimisation lets GCC see the constant index and report it:

#include <stdio.h> int main(void) { int values[3] = {10, 20, 30}; int index = 3; printf("%d\n", values[index]); return 0; }

gcc -O2 -Wall -Wextra bounds.c -o bounds

bounds.c: In function 'main':
bounds.c:8:5: warning: array subscript 3 is above array bounds of 'int[3]' [-Warray-bounds=]
8 | printf("%d\n", values[index]);
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~
bounds.c:5:9: note: while referencing 'values'
5 | int values[3] = {10, 20, 30};
| ^~~~~~

Running it would read the four bytes after the array, which belong to another object or to nothing at all. The value is not shown here because it differs per run and per build; the warning is the reliable part.

Exercises

  1. Declare an array of ten doubles, fill it with the first ten squares, and print it in two columns with a field width.
  2. Reverse an array of six integers in place, swapping the ends, and print it before and after.
  3. Print a five by five identity matrix, with ones on the diagonal and zeros elsewhere, using nested loops.

Questions

A new set of questions in a new order every time. Choose one answer for each, then check.

  1. Which index holds the last element of an array with count elements?

    Answer

    count - 1

    Indexes start at zero, so the valid range is 0 to count - 1 and the count itself is out of bounds.

  2. What happens to the elements left out of a partial initialiser?

    Answer

    They become zero

    A partial initialiser fills the remaining elements with zero, which is why the last two scores print as 0.

  3. How is the number of elements computed from an array that is still in scope?

    Answer

    sizeof array / sizeof array[0]

    The total size divided by the size of one element gives the count; after the array decays to a pointer, sizeof measures the pointer instead.

  4. How are the elements of int grid[3][3] laid out in memory?

    Answer

    As nine contiguous int values

    A two-dimensional array is an array of arrays, stored as one flat block with row-major indexing.

  5. What does this program print? #include <stdio.h> int main(void) { int scores[5] = {90, 75, 88}; printf("%d %d %d %d %d\n", scores[0], scores[1], scores[2], scores[3], scores[4]); return 0; }

    Answer

    90 75 88 0 0

    Three values are given and the remaining two elements are zero-filled by the partial initialiser.

  6. What does this program print? #include <stdio.h> int main(void) { int values[] = {4, 8, 15, 16, 23, 42}; int count = (int) (sizeof values / sizeof values[0]); int sum = 0; for (int i = 0; i < count; i++) sum += values[i]; printf("%d\n", sum); return 0; }

    Answer

    108

    The six values 4, 8, 15, 16, 23 and 42 add up to 108.

  7. What does this program print? #include <stdio.h> int main(void) { int values[] = {4, 8, 15, 16, 23, 42}; int count = (int) (sizeof values / sizeof values[0]); int largest = values[0]; for (int i = 1; i < count; i++) if (values[i] > largest) largest = values[i]; printf("%d\n", largest); return 0; }

    Answer

    42

    The loop keeps the maximum seen so far, and 42 is the largest element of the array.

  8. What does this program print? #include <stdio.h> int main(void) { int scores[5]; printf("%zu\n", sizeof scores); return 0; }

    Answer

    20

    Five int values of four bytes each occupy 20 bytes in total.

  9. This program is built with gcc -std=c17 -Wall -Wextra badinit.c -o badinit. What does GCC warn about? #include <stdio.h> int main(void) { int values[3] = {1, 2, 3, 4}; printf("%d\n", values[0]); return 0; }

    Answer

    excess elements in array initializer

    The initialiser has more values than the declared size, so GCC warns and builds with the first three.

  10. This program is built with gcc -std=c17 -O2 -Wall -Wextra bounds.c -o bounds. Which warning flag does GCC print? #include <stdio.h> int main(void) { int values[3] = {10, 20, 30}; int index = 3; printf("%d\n", values[index]); return 0; }

    Answer

    -Warray-bounds=

    The constant index past the array is reported by -Warray-bounds=.

The code on this page was run with gcc (Alpine 15.2.0) 15.2.0 on Alpine Linux 3.24.3 (x86_64) in a Docker container.

Article author: Arthur Isaev