C · Beginner
Arrays in C
In short: A C array is a fixed-size block of elements of one type stored side by side, declared like int scores[5]; and accessed by a zero-based index from scores[0] to scores[4]. The array does not carry its own length and C never checks that an index is in range, so the program tracks the size itself and keeps every index between 0 and length minus 1.
Many values under one name
Seven days of sales are seven numbers that belong together and are processed the same way. Declaring seven separate variables would force you to repeat every line of code seven times; an array gives all seven one name and lets a loop visit them by number. int loaves[7]; reserves space for seven ints in a row, and loaves[3] is the fourth of them, because indexes start at 0. The index in brackets can be any integer expression, which is what makes loaves[i] inside a loop work.
An array can be filled at declaration with an initialiser list in braces. int stops[] = {3, 7, 12, 18}; lets the compiler count the elements, here four. If you give a size and fewer values, as in double temps[5] = {21.5, 19.0};, the remaining elements are set to zero, and int seen[10] = {0}; is the standard way to zero a whole array. Without any initialiser, a local array holds garbage. The size must be a constant for these forms; C99 also allows variable-length arrays sized at run time, but they are optional in C11 and cannot take an initialiser list, so a fixed maximum is the safer habit.
C does not check indexes. loaves[7] on a seven-element array compiles and runs; it reads or writes the memory just past the end, which belongs to some other variable or to nothing at all. That is undefined behaviour: wrong values, a crash, or a program that works until the day it does not. The array does not remember its size either, so every loop needs the length from somewhere: a constant, a count read from input, or sizeof(arr) / sizeof(arr[0]), which divides the total bytes by the bytes per element. That idiom only works where the array itself is in scope; it is wrong inside a function that received the array as a parameter.
That is because an array is passed to a function as the address of its first element, not as a copy. The parameter int values[] is really a pointer, sizeof on it gives the size of a pointer, and writes through values[i] change the caller's array. So always pass the length alongside the array, and declare the parameter const int values[] when the function should not modify it, so the compiler enforces that promise. Assigning one whole array to another with = is not allowed; copy it element by element or with memcpy from <string.h>.
Two-dimensional arrays, int grid[3][4], are arrays of arrays: three rows of four ints, indexed grid[row][col] and walked with one loop inside another. A string in C is a char array with a terminating zero; it gets its own lesson: strings.
Syntax
type name[SIZE]; /* SIZE elements, contents undefined */
type name[SIZE] = {v0, v1}; /* listed elements set, the rest 0 */
type name[] = {v0, v1, v2}; /* size taken from the list: 3 */
type name[SIZE] = {0}; /* every element 0 */
name[i] /* element i; valid i is 0 .. SIZE-1 */
sizeof(name) / sizeof(name[0]) /* element count, in the declaring scope only */
type grid[ROWS][COLS]; /* two-dimensional; grid[r][c] */
void f(const int values[], int n); /* array parameter: pass the length too */SIZE must be a constant expression for an array that is initialised with a list. #define SIZE 7 at the top of the file is the usual way to name it.
Declaration forms
| Declaration | Elements | Contents |
|---|---|---|
int a[4]; | 4 | undefined (garbage) for a local array |
int a[4] = {5, 6}; | 4 | 5, 6, 0, 0 |
int a[] = {5, 6, 7}; | 3 | 5, 6, 7 |
int a[4] = {0}; | 4 | 0, 0, 0, 0 |
char c[3] = {'a', 'b', 'c'}; | 3 | three chars, not a string: no terminating zero |
Filling an array from input and scanning it
Seven daily loaf counts are read into an array; the input line is 42 38 51 47 60 73 55.
#include <stdio.h>
#define DAYS 7
int main(void)
{
int loaves[DAYS];
for (int i = 0; i < DAYS; i++) {
if (scanf("%d", &loaves[i]) != 1) {
return 1;
}
}
int total = 0;
int best_day = 0;
for (int i = 0; i < DAYS; i++) {
total += loaves[i];
if (loaves[i] > loaves[best_day]) {
best_day = i;
}
}
printf("Total: %d\n", total);
printf("Best day: %d (%d loaves)\n", best_day + 1, loaves[best_day]);
printf("Average: %.1f\n", (double) total / DAYS);
return 0;
}
Input given to the program: 42 38 51 47 60 73 55
Output
Total: 366 Best day: 6 (73 loaves) Average: 52.3
DAYS is a compile-time constant, so it can size the array and bound both loops. The first loop stores each input at loaves[i], passing the element's address to scanf with &loaves[i]. The second loop accumulates the total and tracks the index of the largest value rather than the value itself, so the program can report which day it was; best_day + 1 converts the zero-based index into the day number a person expects.
Initialiser lists and the sizeof idiom
One array is sized by its initialiser and its length is computed; another is partly initialised.
#include <stdio.h>
int main(void)
{
int stops[] = {3, 7, 12, 18};
size_t count = sizeof(stops) / sizeof(stops[0]);
printf("%zu stops\n", count);
for (size_t i = 0; i < count; i++) {
printf("stop %zu at km %d\n", i + 1, stops[i]);
}
double temps[5] = {21.5, 19.0};
for (int i = 0; i < 5; i++) {
printf("%.1f ", temps[i]);
}
printf("\n");
return 0;
}
Output
4 stops stop 1 at km 3 stop 2 at km 7 stop 3 at km 12 stop 4 at km 18 21.5 19.0 0.0 0.0 0.0
sizeof(stops) is 16 bytes on this machine and sizeof(stops[0]) is 4, so the division gives 4 elements. size_t is the correct type for that result, and %zu prints it; making the loop counter a size_t too avoids a signed/unsigned comparison warning. temps was declared with five elements but given only two values, so the other three are 0.0, which the loop prints.
Passing an array to a function
One function counts readings above a limit and leaves the array alone; another doubles every element in place.
#include <stdio.h>
int count_above(const int values[], int n, int limit)
{
int count = 0;
for (int i = 0; i < n; i++) {
if (values[i] > limit) {
count++;
}
}
return count;
}
void scale(int values[], int n, int factor)
{
for (int i = 0; i < n; i++) {
values[i] *= factor;
}
}
int main(void)
{
int readings[6] = {12, 30, 7, 45, 28, 33};
printf("above 25: %d\n", count_above(readings, 6, 25));
scale(readings, 6, 2);
printf("after scaling:");
for (int i = 0; i < 6; i++) {
printf(" %d", readings[i]);
}
printf("\n");
printf("above 60 now: %d\n", count_above(readings, 6, 60));
return 0;
}
Output
above 25: 4 after scaling: 24 60 14 90 56 66 above 60 now: 2
count_above takes const int values[] and the length n; the compiler would reject any assignment to the elements. scale takes the same array without const and modifies it, and main sees the changes because the function received the address of readings[0], not a copy: the opposite of what happens to a plain int parameter (see functions). Passing 6 alongside the array is not optional: inside either function, sizeof(values) would be the size of a pointer, 8, not 24.
Common mistakes
Indexing one past the end
Why it goes wrong: For
int a[5]the valid indexes are 0 to 4.a[5]compiles and touches memory outside the array; the loopfor (i = 0; i <= 5; i++)does exactly that on its last round and the program may print garbage or crash somewhere unrelated.Fix: Loop with
i < size, and treat the size as the number of elements, not the last index.C · fixint a[5] = {0}; for (int i = 0; i < 5; i++) { a[i] = i * i; }Using sizeof on an array parameter
Why it goes wrong: Inside
int total(int values[]),valuesis a pointer, sosizeof(values) / sizeof(values[0])is 8 / 4 = 2 whatever the caller passed. gcc warns withsizeof on array function parameter.Fix: Pass the length as a second parameter and use it.
C · fixint total(const int values[], int n) { int sum = 0; for (int i = 0; i < n; i++) { sum += values[i]; } return sum; }Assigning one array to another
Why it goes wrong:
b = a;does not compile: an array is not an assignable value, andain an expression turns into the address of its first element.Fix: Copy in a loop, or
memcpy(b, a, sizeof a);from<string.h>when both have the same size.C · fixfor (int i = 0; i < n; i++) { b[i] = a[i]; }Counting into an array that was never zeroed
Why it goes wrong:
int counts[10];followed bycounts[d]++adds one to garbage. Local arrays are not zeroed automatically.Fix: Initialise with
= {0}when the array accumulates counts.C · fixint counts[10] = {0}; counts[d]++;
Counting occurrences with an index
An array indexed by the value itself is the quickest way to count how often something occurs. To tally the grades A to E in a class, declare int tally[5] = {0}; and for each grade add one to tally[grade - 'A'], using the fact that consecutive letters have consecutive character codes. The same trick counts digits with tally[d]++, or bins sensor readings into ranges with tally[value / 10]++. Just check that the index is within range before using it: a grade of 'Z' would land 25 elements past the end.
int tally[5] = {0};
char grade;
while (scanf(" %c", &grade) == 1) {
if (grade >= 'A' && grade <= 'E') {
tally[grade - 'A']++;
}
}
for (int i = 0; i < 5; i++) {
printf("%c: %d\n", 'A' + i, tally[i]);
}Key points
type name[N]holds N elements of one type at indexes 0 to N - 1.- Initialiser lists fill from the front; missing elements become 0; no initialiser means garbage.
- C never checks an index; going out of range is undefined behaviour.
sizeof(a) / sizeof(a[0])gives the length only where the array is declared.- A function receives the address of the array, so pass the length too and use
constwhen it must not modify it. - Arrays cannot be assigned with
=; copy element by element or with memcpy. - 2-D arrays are arrays of arrays:
grid[row][col]with nested loops.
Try it yourself
Read five integers into an array and print them in reverse order on one line, separated by single spaces. For the input 4 9 1 7 3 print 3 7 1 9 4.
#include <stdio.h>
int main(void)
{
int values[5];
for (int i = 0; i < 5; i++) {
if (scanf("%d", &values[i]) != 1) {
return 1;
}
}
/* print values[4] down to values[0], separated by spaces */
return 0;
}
4 9 1 7 33 7 1 9 4#include <stdio.h>
int main(void)
{
int values[5];
for (int i = 0; i < 5; i++) {
if (scanf("%d", &values[i]) != 1) {
return 1;
}
}
for (int i = 4; i >= 0; i--) {
printf("%d", values[i]);
if (i > 0) {
printf(" ");
}
}
printf("\n");
return 0;
}
Practise this
Exercises for this lesson are in the C practice set.
Related lessons
- Loops in C: for, while and do-whileThe three C loops and when each fits: how a for loop's three parts run, reading input until it ends with while and scanf, and using break and continue safely.9 min
- Functions in CHow to define and call C functions: prototypes before the first call, arguments copied by value, void functions, early returns and local variables.10 min
- Strings in CHow C stores text as null-terminated char arrays, reads a line safely with fgets, and copies, joins, compares and searches text with the string.h functions.9 min
- Pointers in CWhat a C pointer is, how & and * work, why functions take addresses to change their caller's variables, and how pointer arithmetic walks an array.10 min
- Sorting and Searching in CLinear and binary search, insertion sort by hand, and the C library's qsort and bsearch with comparison functions, applied to arrays of ints and structs.10 min
Frequently asked questions
How do I get the length of an array in C?
There is no length property. Where the array is declared, sizeof(arr) / sizeof(arr[0]) computes the element count from the byte sizes. Anywhere else, in particular inside a function that received the array as a parameter, that expression gives the size of a pointer instead, so the length has to be passed along explicitly or kept in a variable next to the array.
Why does C not report an error when I read past the end of an array?
Bounds checks cost time on every access and C leaves that decision to the programmer, so the language defines an out-of-range index as undefined behaviour rather than an error. The compiler may warn when the index is a constant it can see, and tools such as gcc's -fsanitize=address catch the mistake at run time during testing, but in a normal build the program simply reads or writes whatever memory lies there.
Can the size of an array be a variable?
With a variable-length array, int a[n]; where n is known only at run time, yes: C99 introduced them and gcc supports them, but C11 made them optional and they cannot be initialised with a list. For exercises the simplest route is a fixed maximum size from the input limits, such as int a[1000];, plus a count variable for how many elements are in use. Memory sized exactly at run time is the job of malloc, covered in dynamic memory.
How this page was checked. Every program on it was run with GCC 13 (C11) at build time by the publishing checks, and the output shown is what it printed. Running C inside the browser is not available yet, so the Run button is absent rather than pretending; copy the code and run it with GCC 13 (C11) locally.