Static One Dimensional Arrays in C

Many applications require the processing of many data that have the same characteristics. For example, a set of numerical data represented by x_1, x_2, \dots x_n. In such situations it is very convenient to place the data in an array.

Arrays are one of the most important data structures in programming. An array is a set of elements of the same type that can be accessed through an index. In C language, arrays can be static or dynamic, and in this lesson we will focus on static one dimensional arrays.

Static one dimensional arrays in C are declared by specifying the data type of the elements and the number of elements inside the array. Once declared, the static array has a fixed size and cannot be modified in the future. The elements of an array are contiguous in memory and can be accessed using the index operator [].

Assigning values to the elements of a static array in C is simple: it is sufficient to use the assignment operator (=) and specify the index of the element. It is also possible to use a loop to assign values to the elements of the array, for example using a for loop.

Accessing the elements of a static array in C is equally simple: it is sufficient to use the index operator [] and specify the index of the element. In this lesson we will see all these concepts and provide practical examples to help better understand static one dimensional arrays in C.

Key Takeaways
  • Declaration of a static one dimensional array: how to specify the data type of the elements and the number of elements inside the array;
  • Assignment of values to the elements of the array: how to use the assignment operator (=) and indices to assign values to the elements of the array;
  • Access to the elements of the array: how to use the index operator [] to access the elements of the array;
  • Use of loops to access and assign values to the elements of the array: how to use loops, such as for, to work with the elements of the array;
  • Contiguous memory: the elements of an array are contiguous in memory, which makes access efficient;
  • Fixed size: once declared, the static array has a fixed size and cannot be modified in the future;

Static one dimensional Arrays

An array is a data structure that contains a certain number of data which are all of the same type. These data are called elements of the array and can be selected individually based on their position inside the array.

The simplest type of array is the one dimensional one, that is with a single dimension. Conceptually, the elements of an array of this type are organized, or rather arranged in memory, one adjacent to the other in succession to form a single row (or column depending on how you visualize them). For example, an array named x of 10 elements can be visualized in this way:

Static one dimensional Array
Picture 1: Static one dimensional Array

The number of elements that an array can contain inside it can be fixed in advance or change at execution time, that is change during the execution of the program itself.

In the first case we speak of static arrays while in the second case of dynamic arrays.

To be able to work with dynamic arrays, additional knowledge of memory management mechanisms is required and above all knowledge of pointers is required, which we will see in the next lessons. For the moment we will concentrate on the study of static arrays for which the number of elements is fixed at compile time.

Definition

Static Arrays

A static array is a data structure composed of a number of homogeneous elements, that is of the same type. These elements are arranged in memory in a sequential way, that is one adjacent to the other to form a contiguous row (or column) of elements.

The elements of an array can be accessed using one or more indices. When the index is only one we speak of one dimensional Arrays.

A static array has a number of elements fixed in advance, that is at compile time. The number of elements cannot be modified dynamically.

Declaration of a Static Array

To be able to declare a static array in C language it is necessary to specify both the type of the elements of the array, and the number of elements that the array contains.

The general syntax to do this is the following:

element_type array_name[number_of_elements];

So, wanting to declare an array named x that contains 10 elements of type int we can write in this way:

int x[10];

The elements of an array can be of any type while the length of an array must always be specified using a constant expression that translates to a positive integer. In particular it is not possible to use an expression whose result is known only at execution time.

Definition

Declaration of a Static Array

The syntax for declaring a static array is the following:

type name[constant_expression];
  • The type can be any valid type of the C language;
  • the expression constant_expression must be any valid integer-valued expression whose result must be known at compile time.

Indexing an Array

To access a particular element of an array you use indexing.

Syntactically, it is about specifying the name of the array followed by an integer value enclosed in square brackets.

The fundamental thing to remember is that the elements of an array are numbered starting from zero, 0 and not from 1. So, if we have an array with n elements, the latter will be indexed with indices ranging from 0 to n - 1.

For example, suppose we declare an array of 10 integer elements:

int x[10];

If we want to access the fifth element we must use the number 4 as index, so:

printf("Fifth element: %d\n", x[4]);

The elements of the array x will be indicated by the syntax: x[0], x[1], x[2], \dots, x[9].

To remember well the fact that the indices of an array start from zero you must understand the value not so much as an index but as a displacement or offset. If we imagine the name of an array as the label of a memory location (which in reality it is) then the value between square brackets represents the number of cells you need to move to reach the desired element.

So, to access the first element I must move zero cells relative to the head of the array and so on.

Expressions of the form x[i] are treated as variables and can be used as such. For example we can modify the value of the first element of the array x in this way:

/* Modification of the first element */
x[0] = 1;

And we can recall the value of an element of the array in the same way:

/* We assign the value of the third element to the variable y */
int y = x[2];

To access an element of an array we can use any expression that translates to an integer value greater than or equal to zero.

We cannot, instead, use numerical values of other types such as for example a floating point.

/* The following code is valid */
int i = 2;
int j = 3;

x[i + j * 2] = 1;

/* The following code is NOT valid */
float k = 1.0F;
x[k] = 0; /* ERROR */

Array Limits

A fundamental thing to keep in mind when using arrays in C language is that the limits of an array are not checked automatically.

Note

The limits of an array are not checked automatically

The C language standard does not impose that the limits of an array be checked when accessing an element of the array itself.

In case the index of an array is out of range the behavior of the program becomes unpredictable.

Let's take the following code excerpt:

/* We declare an array with 10 elements */
int x[10];

/* We access the eleventh element */
x[10] = 1;

In this case the line x[10] = 1; accesses the eleventh element which does not exist.

In practice the program will access the memory location that immediately follows the tenth element of the array. Since this memory location could belong to another variable or control structure we cannot know in advance what will happen to our program. For this reason the behavior of the program is unpredictable: nothing might happen or our program might crash.

In case the program crashes we speak of Segmentation Fault or Segmentation Error.

You must pay close attention when working with the indices of an array in C language. Writing beyond the limits of an array is called Array Overflow or Array Overflow.

Some programming languages like Java, C#, Python and Rust control access to arrays and report overflows to make programs more robust.

The designers of C decided not to insert mechanisms for controlling array overflow to make C programs faster. In fact controlling the limits of an array has a cost on the performance of a program. Obviously this choice has its pros and cons.

Recapping:

Definition

Array Overflow or Array Overflow

The Array Overflow in C language occurs when you try to access an element of an array with an index that exceeds the number of elements of the array itself.

In C language no check is made on the indices of an array so accessing a location beyond the limits is not, in itself, an illegal operation.

However the consequences can be unpredictable. In many cases the program might crash and the related event is called Segmentation Fault or Segmentation Error.

Arrays and Loops

In C language arrays are closely related to loops, in particular to for loops.

The vast majority of programs that deal with arrays have inside them excerpts of code to initialize, read, modify and operate on the elements of an array. Precisely because operations of this type are very frequent, in this section we report some idiomatic examples of using for loops to operate on arrays.

Initialize an Array with a for Loop

In C language as well as for all types of variables also arrays when they are declared are never initialized automatically. Reason why, often, the need arises to initialize the elements of an array to a starting value.

A typical example of initializing the elements of an array to zero with a for loop is the following:

int x[10];
int i;

for (i = 0; i < 10; ++i) {
    x[i] = 0; /* Initialize the elements to zero */
}

Example of Loading Data in an Array

Another typical example of a for loop that operates on an array is to load the values of an array from keyboard input through scanf.

In the following example 10 floating point values are read from keyboard and stored in an array:

double x[10];
int i;

for (i = 0; i < 10; ++i) {
    scanf("%lf", &x[i]);
}

Cumulative Operations on an Array

Often it happens to have to perform cumulative operations on an array, that is operations that need to calculate a value starting from all the elements of an array.

An example is the calculation of the sum of all elements of type double contained in an array. To do this just combine a for loop with the use of a support variable that is called accumulator:

double x[10];

/* Accumulator variable. Initialized to zero */
double accumulator = 0.0;

int i;

/* Storage of values in the array */
/* ... */

/* Cumulative sum operation */
for (i = 0; i < 10; ++i) {
    accumulator += x[i];
}

printf("Total sum: %f\n", accumulator);

Array Initialization

An array, like any other type of variable, can be initialized at the time of declaration.

However, the rules for initialization are quite insidious, which is why in this section we will see them in detail.

The simplest form of initialization for arrays is a list of values separated by commas and enclosed in curly braces. This list is called initialization list of an array and has the following syntax:

array_type array_name[number_of_elements] = {element_1, element_2, ..., element_n};

For example, assuming we have an array of 5 integer numbers, one way to initialize it is the following:

int x[5] = {10, 20, 30, 40, 50};

In case the list contains fewer elements than the array contains, the remaining elements of the array are initialized to zero. For example:

int x[10] = {10, 20, 30, 40, 50};

In this case, the array x is of 10 elements but in the initialization list there are only 5 elements. So the array x will contain inside it the values:

{10, 20, 30, 40, 50, 0, 0, 0, 0, 0}

The last five values will be equal to zero.

A very useful trick is to exploit this functionality to initialize all values of the array to zero, in this way:

int x[10] = {0};

In this way the initialization list is composed of a single element equal to zero. The remaining elements will still be initialized to zero. So the array x will contain all values initialized to zero:

{0, 0, 0, 0, 0, 0, 0, 0, 0, 0}

We still had to insert a single value inside the initialization list because in C language it is illegal to have an empty initialization list. We could not have written the line of code in this way:

/* SYNTAX ERROR */
int x[10] = {};

Another situation not allowed in C language is when the initialization list has more elements than the array. For example the following code is illegal:

/* ERROR: The initialization list has 5 elements */
int x[4] = {10, 20, 30, 40, 50};

In the example the array x is declared of four elements but the initialization list has five.

To overcome this problem, in C language it is allowed to omit the length of the array when an initialization list is present. For example:

int x[] = {10, 20, 30, 40, 50};
/* The array x will contain 5 elements */

In this case we did not specify between the square brackets the number of elements of the array x but the compiler deduced that the array contained 5 elements from the length of the initialization list.

Recapping:

Definition

Initialization List

A static array can be initialized in C using an initialization list.

The syntax of an initialization list is the following:

type name[number_of_elements] = {element_1, element_2, ..., element_n};
  • An initialization list cannot be empty;
  • An initialization list cannot have a number of elements greater than the number of elements of an array;
  • In case an initialization list has fewer elements than the number of elements of an array, the remaining elements are initialized to the default value of the type;
  • When using an initialization list, the size of the array can be omitted. In this case the size is deduced from the size of the list.

Designated Initializers in C99

Often, it can happen that only a few elements of an array need to be initialized with a specific value while the majority must be initialized with a default value or zero.

Let's take for example an array x of 20 elements of which only 4 elements need to be initialized to a value different from zero. We could write in this way:

int x[20] = {0, 0, 0, 0, 61, 0, 0, 0, 39, 0,
             0, 0, 0, 0, 0, 24, 0, 0, 56, 0};

In this example we initialized the fifth element to 61, the ninth element to 39, the sixteenth element to 24 and the nineteenth element to 56. All other elements we initialized to zero.

When the dimensions of an array become large, writing an initialization list in this way becomes not only tedious but subject to errors. Think, for example, of an array of 100 elements of which we want to initialize only a handful.

To solve this problem, in the C99 standard Designated Initializers were introduced that can be used in initialization lists. Returning to the previous example we can rewrite the initialization of the array in this way:

int x[20] = {[5] = 61, [9] = 39, [16] = 24, [19] = 56};

As can be seen, in the initialization list we specified the index of the value between square brackets followed by the equal sign and the initialization value. All elements not present in the list are automatically initialized to zero.

The advantage of using designated initializers in C99 is that it is more readable and less subject to errors. Furthermore, the initialization order no longer counts. Therefore we can rewrite the line above also in this way:

int x[20] = {[9] = 39, [5] = 61, [19] = 56, [16] = 24};

Obviously, a designated initializer must be a constant integer expression, that is whose value is known at compile time. If the array is of length n, all designated initializers must have a value between 0 and n - 1.

Designated initializers can also be used in the case where the length of the array is omitted. In this case the length of the array is deduced from the designator with the largest index.

For example:

/* The array will have length 15 */
int x[] = {[1] = 7, [14] = 22, [5] = 9};

In the example above we omitted the length of the array. In this case the compiler deduces that the length of the array must be 15 (that is 14 plus one) elements since 14 is the index of the largest designated initializer.

Finally, it is possible to combine the old initialization style with designated initializers. For example:

int x[10] = {34, 23, [5] = 78, 54, [8] = 99};

When the compiler finds a value without a designated initializer, it assigns the value to the element following that of the last designated initializer.

In this example the array x will have the following values:

{34, 23, 0, 0, 0, 78, 54, 0, 99, 0}

All elements not specified in the list will be initialized to zero.

Recapping:

Definition

Designated Initializers in C99

In the C99 standard it is possible to insert designated initializers inside initialization lists.

The syntax of a designated initializer is the following:

[index] = value
  • The value of the index must be between 0 and the size of the array minus 1;
  • Using designated initializers the order does not count;
  • If the size of the array is omitted, the highest index among the initializers is used as size;
  • An initialization list can contain both values and designated initializers. In this case the values without a designated initializer are assigned to elements with index starting from the one following that of the nearest designated initializer, or starting from zero.

Arrays and sizeof Operator

The sizeof operator can be used to get the size of a static array in the same way as a normal variable.

For example, assuming that the type int occupies 64 bits, we can calculate the size of an array of 10 integer elements in this way:

int x[10] = {0};
printf("Array size: %d byte\n", sizeof(x));

Executing this excerpt of code we get the following output:

Array size: 80 byte

In fact, the final result is 80 bytes, since an integer int occupies 8 bytes (if we are on a 64-bit machine) so 8 \cdot 10 = 80 bytes. On a 32-bit machine the result might be different.

The sizeof operator can also be used to understand how many elements a static array contains. Returning to the previous example we can write in this way:

int number_of_elements;
number_of_elements = sizeof(x) / sizeof(x[0]);

This expression works because sizeof(x[0]) reports the size only of the first element of an array. Reason why we are dividing the total size of an array by the size of a single element.

Using this technique we can avoid explicitly writing the size of an array in a for loop. For example to perform the sum of all elements of an array we can write in this way:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <stdio.h>

int main() {
    int x[10] = {0};
    int i;
    int sum = 0;

    /* Reads from console the values of the array */
    for (i = 0; i < (sizeof(x) / sizeof(x[0])); ++i) {
        printf("Insert value %d: ", i + 1);
        scanf("%d", &x[i]);
    }

    /* Performs the sum */
    for (i = 0; i < (sizeof(x) / sizeof(x[0])); ++i) {
        sum += x[i];
    }

    printf("Total sum: %d\n", sum);

    return 0;
}

As you can see from the example, in lines 9 and 15 we did not have to directly specify the length of our array. In this way, if we wanted to modify the code to handle arrays with a different number of elements, it would be enough to change the size of the array at line 4.

Using this technique, the only problem that could arise is that many compilers might complain about the expression i < (sizeof(x) / sizeof(x[0])). For example, using gcc and activating the options -Wextra -Wall which enable extra warning notifications, we could get the following warning messages:

test.c: In function 'main':
test.c:9:19: warning: comparison of integer expressions of different signedness: 'int' and 'long unsigned int' [-Wsign-compare]
    9 |     for (i = 0; i < (sizeof(x) / sizeof(x[0])); ++i) {
      |                   ^
test.c:15:19: warning: comparison of integer expressions of different signedness: 'int' and 'long unsigned int' [-Wsign-compare]
   15 |     for (i = 0; i < (sizeof(x) / sizeof(x[0])); ++i) {
      |  

With these messages, the compiler is telling us that we are comparing a signed int, the variable i, with the result of an unsigned operation. In fact the result of a sizeof operator is a size_t value, that is an unsigned integer. Comparing an unsigned integer and a signed integer is not always a good idea.

We can avoid this warning by rewriting the for loop using an explicit cast in this way:

for (i = 0; i < (int) (sizeof(x) / sizeof(x[0])); ++i) {

So the program becomes:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <stdio.h>

int main() {
    int x[10] = {0};
    int i;
    int sum = 0;

    /* Reads from console the values of the array */
    for (i = 0; i < (int) (sizeof(x) / sizeof(x[0])); ++i) {
        printf("Insert value %d: ", i + 1);
        scanf("%d", &x[i]);
    }

    /* Performs the sum */
    for (i = 0; i < (int) (sizeof(x) / sizeof(x[0])); ++i) {
        sum += x[i];
    }

    printf("Total sum: %d\n", sum);

    return 0;
}

At this point the compiler will no longer complain since we have made explicit the fact that we are comparing a signed integer with an unsigned one.

In Summary

In this lesson we saw how to create and use static one dimensional arrays in C. We discussed how to declare an array, how to assign values to the elements and how to access them using the index operator []. We also provided practical examples to show how these concepts can be used in real programming.

Static one dimensional arrays are one of the most important and versatile data structures in C and are used in many types of programs. It is important to know how to use them effectively to make the most of their potential.

However, static one dimensional arrays have some limitations, including the fixed size and the impossibility of modifying it once declared. In future lessons we will discuss other types of arrays and how to overcome these limitations.

In the next lesson we will deepen the study of static multidimensional arrays whose elements are indexable with multiple indices.