Arrays

An array is an indexed collection of elements of the same type. Sometimes you might want to create many similar variables such as int a1, a2, a3;–this is when you want to create an array instead:

int arr[10];

When you declare a variable this way, you actually get 10 int variables: a[0], a[1], a[2], ..., a[9]–note that array indices start from 0 in C. They are allocated in a contiguous block of memory, which is very important and we will discuss it very soon.

Arrays can be initialized when they are declared:

int arr[10] = { 5, 5, 5, 5, 5, 4, 4, 4, 4, 4 };

After this declaration, a[0] is 5, and a[9] is 4. Note that you cannot assign to an array, you can only initialize it once.

After an array is initialized, you can assign values to its elements individually, and use them as you wish.

If your initializer is incomplete (has less elements than you allocated for your array), the remaining elements will be assigned to 0. Making your new array filled with zeros is easy:

int zeros[100] = { 0 }; /* incomplete initializer: remaining elements are zeros */

Remember our number_of_days function which returns number of days in a given month? Now we can rewrite it using an array:

int days[13] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };

int number_of_days(int y, int m) {
	if (m == 2 && is_leap_year(y)) {
		return 29; /* special case */
	}
	return days[m];
}

Note that since arrays indexes start with 0, and months start with 1, we allocate 13 elements for the days array and never use days[0]. An alternative option is to allocate 12 elements (0-th to 11-th) and use days[m - 1] in the function.

Pointer arithmetic

Let's talk about pointers a little bit more, because pointers and arrays in C are very closely related. Imagine we have an array, int arr[10]. We can get an address of its 0-th element:

int *p = &a[0];

In C, you can add a number to a pointer (and also subtract a number from a pointer). If p points to a[0], then p + 1 will actually point to a[1], p + 2 will point to a[2], and so on.

If we look at the actual address stored in p, incrementing it by 1 will increment an address by the size of one int value, which is 4 bytes on most modern systems. Let's see:

#include <stdio.h>

int main() {
	int arr[10] = { 0 };
	int *p = &arr[0];
	printf("p:     %d\n", p);
	printf("p + 1: %d\n", p + 1);
	return 0; 
}

Having a pointer p which points to the 0-th element of arr, you can dereference it with *p which will give you the value it points to, which is a[0]. Same thing, *(p + 1) is a[1], and so on.

The next fun thing here is that you can use the name of your array, arr, as a pointer, and assign it to a pointer. This is called decay: we say that an array "decays" to a pointer when assigned:

int *p = arr;  /* same as &a[0] */

Why is it a "decay"? Because p does not have any information about the size of the array: p does not know that there are 10 elements in total. It will be important when we talk about passing arrays to functions.

Now, it looks like we can just use arr as a pointer. Indeed: *arr dereferences array-name-as-a-pointer to get arr[0]. Similarly, arr + 1 uses pointer arithmetic to point to arr[1], and *(arr + 1) is the value of arr[1]. In general, the following equation holds:

arr[i] == *(arr + i)

I'll tell you one more scary thing, just promise that you will never write your code like this: array indexing operator [ ] is just a combination of pointer arithmetic and dereference, and since arr + i is the same as i + arr, we can actually write... i[arr] instead of arr[i], and even 1[arr] instead of arr[1]. Just try it if you don't believe me:

#include <stdio.h>

int main() {
	int arr[3] = { 10, 20, 30 };
	printf("%d %d %d\n", arr[0], arr[1], arr[2]);
	printf("%d %d %d\n", *arr, *(arr + 1), *(arr + 2));
	printf("%d %d %d\n", 0[arr], 1[arr], 2[arr]); /* don’t try this at home! */
	return 0;
}

Array size is fixed

I'll tell you one more thing before we move on to our first array exercise. In classic C, the size of the array–the number of its elements–is always a constant. You must know how big your data is when you decide how many elements you need. Classic C does not have "variable length arrays", and if you want to allocate memory of the size you don't know when you're writing the program, you need to use other techniques (such as using malloc function, which we'll discuss later).

In most exercises in this course, you will be told how big your data is going to be, so you won't need to guess how many elements to allocate.

Let's jump to the exercise!