Introduction

An array is a collection of values of the same type stored in a sequential manner. Arrays are represented by a variable name followed by square brackets, with the number inside the brackets indicating the number of elements in the array.

1
int scores[100];

In this example, an array named scores is declared with 100 elements, each of which is of type int.

Note: When declaring an array, you must specify its size.

Array elements are indexed starting from 0. Thus, in an array scores[100], the indices range from 0 to 99. The last element’s index is always one less than the array’s length.

You can access and assign values to array elements using their index in square brackets:

1
2
scores[0] = 13;
scores[99] = 42;

The above example assigns values to the first and last positions of the scores array.

Note: Accessing an out-of-bounds index (i.e., accessing an element that does not exist) does not produce an error, so you must be very careful to avoid such out-of-bounds access.

1
2
3
int scores[100];

scores[100] = 51;

In this example, the array scores only has 100 elements, so scores[100] is out of bounds. Accessing this index will not produce an error but will modify memory adjacent to the array, potentially altering other variables. This can lead to difficult-to-diagnose errors.

Arrays can also be initialized using curly braces at the time of declaration:

1
int a[5] = {22, 37, 3490, 18, 95};

Note: When initializing an array with curly braces, you must do so at the time of declaration. Initializing an already declared array with curly braces will result in a compilation error.

1
2
int a[5];
a = {22, 37, 3490, 18, 95}; // Error

The error occurs because, once an array is declared, its memory address cannot be changed, as will be explained later. Therefore, you cannot reassign values to an already declared array using curly braces.

1
2
int a[5] = {1, 2, 3, 4, 5};
a = {22, 37, 3490, 18, 95}; // Error

Reassigning values using curly braces after an initial declaration is also not allowed.

When using curly braces for initialization, if you provide more values than the array’s size, a compilation error will occur.

If you provide fewer values than the array size, the remaining elements will be automatically initialized to 0.

1
2
3
int a[5] = {22, 37, 3490};
// Equivalent to
int a[5] = {22, 37, 3490, 0, 0};

To initialize an entire array to zero, you can use:

1
int a[100] = {0};

You can also specify values for specific positions in an array:

1
int a[15] = {[2] = 29, [9] = 7, [14] = 48};

In this example, only the 2nd, 9th, and 14th positions are assigned values; all other positions default to 0.

Assignments to specific positions can be done in any order:

1
int a[15] = {[9] = 7, [14] = 48, [2] = 29};

You can combine specific position assignments with sequential initialization:

1
int a[15] = {1, [5] = 10, 11, [10] = 20, 21};

In this example, elements at positions 0, 5, 6, 10, and 11 are assigned values.

C allows you to omit the size of an array during declaration. In this case, the array’s length is automatically determined by the number of values provided:

1
2
3
int a[] = {22, 37, 3490};
// Equivalent to
int a[3] = {22, 37, 3490};

Here, the length of the array a is 3, based on the number of values in the curly braces.

When using specific position assignments with omitted sizes, the array length will be one more than the highest specified index:

1
int a[] = {[2] = 6, [9] = 12};

In this case, the highest specified index is 9, so the array length is 10.

Array Length

In C, the sizeof operator returns the total byte size of an array.

1
2
int a[] = {22, 37, 3490};
int arrLen = sizeof(a); // 12 bytes

In this example, sizeof(a) returns the total byte size of the array a, which is 12 bytes.

Since all elements in the array are of the same type, each element occupies the same number of bytes. Therefore, to determine the number of elements in the array, you divide the total byte size of the array by the byte size of a single element:

1
sizeof(a) / sizeof(a[0])

In this case, sizeof(a) gives the total byte size of the array, while sizeof(a[0]) provides the byte size of a single element. Dividing these values gives the number of elements in the array.

Note that sizeof returns a value of type size_t, so the result of sizeof(a) / sizeof(a[0]) is also of type size_t. Use %zu or %zd format specifiers in printf to print size_t values.

1
2
3
4
5
int x[12];

printf("%zu\n", sizeof(x)); // 48 bytes
printf("%zu\n", sizeof(int)); // 4 bytes
printf("%zu\n", sizeof(x) / sizeof(int)); // 12 elements

In this example, sizeof(x) / sizeof(int) computes the number of elements in the array, which is 12.

Multidimensional Arrays

C allows you to declare arrays with multiple dimensions by using multiple sets of square brackets. For example, a two-dimensional array is declared with two sets of square brackets:

1
int board[10][10];

Here, board is a two-dimensional array with 10 elements in each dimension, resulting in a total of 100 elements (10 x 10).

In a multidimensional array, each element of the higher dimension is itself an array. For instance, in board[10][10], each of the 10 elements in the first dimension is an array of 10 elements.

To reference elements in a two-dimensional array, you use two sets of square brackets to specify the row and column:

1
2
board[0][0] = 13;
board[9][9] = 13;

Note that board[0][0] should not be written as board[0, 0], because 0, 0 is a comma-separated expression and evaluates to 0, making board[0, 0] equivalent to board[0].

Just like with one-dimensional arrays, indexing in multidimensional arrays starts at 0.

Multidimensional arrays can also be initialized with nested braces:

1
2
3
4
int a[2][5] = {
{0, 1, 2, 3, 4},
{5, 6, 7, 8, 9}
};

In this example, a is a two-dimensional array. This initialization syntax assigns values to the array elements, with any omitted values automatically set to 0.

You can also initialize specific elements using designated initializers:

1
int a[2][2] = {[0][0] = 1, [1][1] = 2};

Here, only the elements at [0][0] and [1][1] are set to 1 and 2 respectively, with all other elements defaulting to 0.

Regardless of the number of dimensions, arrays are stored in memory in a linear fashion. For example, in a 2D array, elements are stored row by row. Therefore, you can also use a single set of braces for initialization:

1
int a[2][2] = {1, 0, 0, 2};

This syntax is equivalent to the previous initialization methods, with values assigned in row-major order.

Variable-Length Arrays (VLAs)

When declaring arrays, their length can be specified with a variable, not just a constant. This is known as a variable-length array (VLA).

1
2
int n = x + y;
int arr[n];

In this example, arr is a VLA because its length depends on the variable n, which is determined at runtime.

A VLA is characterized by having its length only known at runtime. This allows programmers to allocate arrays with precise lengths at runtime instead of guessing a size during development.

Any array whose length is determined at runtime is a VLA.

1
2
3
4
int i = 10;
int a1[i];
int a2[i + 5];
int a3[i + k];

In these examples, the lengths of a1, a2, and a3 are only known at runtime, so they are all VLAs.

VLAs can also be used in multidimensional arrays.

1
2
3
int m = 4;
int n = 5;
int c[m][n];

Here, c[m][n] is a two-dimensional VLA.

Array Addresses

Arrays consist of a sequence of contiguous values of the same type. By obtaining the address of the first element, you can calculate the addresses of the other elements.

1
2
3
4
5
int a[5] = {11, 22, 33, 44, 55};
int* p;

p = &a[0];
printf("%d\n", *p); // Prints "11"

In this example, &a[0] is the address of the first element of a, and *p retrieves its value.

Since getting the starting address of an array is a common operation, C provides a shorthand: the array name itself represents the address of the first element.

1
2
3
int a[5] = {11, 22, 33, 44, 55};
int* p = &a[0]; // Equivalent to
int* p = a;

Here, &a[0] and a are equivalent.

Passing an array to a function is the same as passing a pointer to its first element. You can declare a function to accept an array like this:

1
2
3
4
// Option 1
int sum(int arr[], int len);
// Option 2
int sum(int* arr, int len);

The function prototype with arr[] or *arr is interchangeable. Here’s how you can sum the elements using a pointer:

1
2
3
4
5
6
7
8
int sum(int* arr, int len) {
int i;
int total = 0;
for (i = 0; i < len; i++) {
total += arr[i];
}
return total;
}

Here, the function takes a pointer arr (which is also the array name) and an integer len for the array length, summing the array elements.

Pointer operations also apply to multidimensional arrays.

1
2
3
int a[4][2];
// Accessing a[0][0]
*(a[0]); // Equivalent to **a

In this case, a[0] is a pointer to the first row of the array, and **a accesses a[0][0].

Note that the array name is fixed and cannot be reassigned.

1
2
int ints[100];
ints = NULL; // Error

Reassigning the array name will cause an error.

Similarly, you cannot assign one array to another.

1
2
3
4
5
6
7
8
int a[5] = {1, 2, 3, 4, 5};

// Option 1
int b[5] = a; // Error

// Option 2
int b[5];
b = a; // Error

Both attempts to reassign the array address will result in an error.

Here’s a refined version of the explanation in English:

Pointer Arithmetic with Arrays

In C, you can perform addition and subtraction operations on array pointers. This effectively moves between the memory addresses of array elements. For instance, a + 1 gives the address of the next element, while a - 1 gives the address of the previous element.

Consider the following array and loop:

1
2
3
4
5
int a[5] = {11, 22, 33, 44, 55};

for (int i = 0; i < 5; i++) {
printf("%d\n", *(a + i));
}

In this example, we use pointer arithmetic to traverse the array. The expression a + i points to the address of the i-th element, and *(a + i) retrieves the value at that address, which is equivalent to a[i]. For the first element, *(a + 0) (or *a) is the same as a[0].

Since array names and pointers are equivalent, the following equality always holds:

1
a[b] == *(a + b)

This demonstrates two ways to access array elements: using the subscript notation a[b] and pointer dereferencing *(a + b).

If a pointer variable p points to an array element, then p++ will point to the next element. This is commonly used to traverse arrays:

1
2
3
4
5
6
7
8
int a[] = {11, 22, 33, 44, 55, 999};

int* p = a;

while (*p != 999) {
printf("%d\n", *p);
p++;
}

In this example, p++ moves the pointer to the next element.

Note that the address of the array itself (a) cannot be modified, so a++ is invalid. Instead, assign the array’s address to a pointer variable and increment the pointer:

1
2
3
4
5
6
7
8
int a[] = {11, 22, 33, 44, 55, 999};

int* p = a;

while (p < a + 6) {
printf("%d\n", *p);
p++;
}

Calculating Array Length

You can also compute the length of an array by comparing pointers:

1
2
3
4
5
6
7
8
9
10
11
12
13
int sum(int* start, int* end) {
int total = 0;

while (start < end) {
total += *start;
start++;
}

return total;
}

int arr[5] = {20, 10, 5, 39, 4};
printf("%i\n", sum(arr, arr + 5));

Here, arr is the start address and arr + 5 is the end address. As long as the start address is less than the end address, the function accumulates the values.

To find the number of elements between two pointers:

1
2
3
4
5
6
7
int arr[5] = {20, 10, 5, 39, 88};
int* p = arr;

while (*p != 88)
p++;

printf("%i\n", p - arr); // 4

The difference p - arr gives the number of elements between the start of the array and the pointer p.

Pointer Arithmetic with Multidimensional Arrays

For multidimensional arrays, pointer arithmetic works differently depending on the dimension:

1
2
3
4
5
6
7
int arr[4][2];

// Pointer points to arr[1]
arr + 1;

// Pointer points to arr[0][1]
arr[0] + 1

In this example, arr is a 2D array. arr + 1 moves the pointer to the next 1D array (i.e., arr[1]). Since each 1D array is an array of integers, arr[0] + 1 moves the pointer to the next integer in the first 1D array, which is arr[0][1].

Array Copying

Since array names are pointers, you cannot directly copy arrays by assigning their names:

1
2
3
4
int* a;
int b[3] = {1, 2, 3};

a = b;

In this case, a and b point to the same array. To copy arrays, you need to copy each element individually:

1
2
for (int i = 0; i < N; i++)
a[i] = b[i];

Alternatively, you can use memcpy() from the string.h library, which is faster:

1
2
3
#include <string.h>

memcpy(a, b, sizeof(b));

This copies the entire memory block occupied by array b to array a.

Passing Arrays to Functions

When passing arrays to functions, you typically provide both the array name and its length.

1
2
3
4
5
6
int sum_array(int a[], int n) {
// ...
}

int a[] = {3, 5, 7, 3};
int sum = sum_array(a, 4);

In this example, sum_array() takes the array a and its length 4 as parameters. The array name alone (i.e., a) is a pointer to the first element, so the function only knows where the array starts, not its size. Hence, you need to pass the length as well.

For multidimensional arrays, you need to specify the size of all but the first dimension in the function definition.

1
2
3
4
5
6
7
8
9
int sum_array(int a[][4], int n) {
// ...
}

int a[2][4] = {
{1, 2, 3, 4},
{8, 9, 10, 11}
};
int sum = sum_array(a, 2);

Here, sum_array() takes a 2D array with the second dimension size specified (4). The first dimension (2) is passed as a separate parameter, and the function knows each element’s size because the second dimension is provided.

Variable-Length Arrays

When using variable-length arrays (VLAs) as parameters, the syntax changes slightly:

1
2
3
4
5
6
int sum_array(int n, int a[n]) {
// ...
}

int a[] = {3, 5, 7, 3};
int sum = sum_array(4, a);

In this case, the array’s length depends on the value of n, which must be passed before the VLA parameter. The function prototype can omit variable names:

1
2
int sum_array(int, int []);
int sum_array(int, int [*]);

For multidimensional arrays, you can omit the size of the last dimension:

1
int sum_array(int n, int m, int a[n][m]);

Array Literals as Parameters

You can pass array literals directly to functions:

1
2
3
4
5
int sum_array(int a[], int n) {
// ...
}

int sum = sum_array((int []){2, 3, 4, 5}, 4);

This approach avoids declaring a separate array variable, directly passing the literal {2, 3, 4, 5} as an array.