Dr. Roger Ianjamasimanana

How to use arrays in C programming?

By Dr. Roger Ianjamasimanana

1. What is an array?

An array is a collection of elements of the same type stored in contiguous memory locations. Arrays provide efficient ways to store and manipulate collections of data by allowing for efficient indexing and iteration.

2. Arrays declaration and initialization

Arrays can be declared by specifying the type of their elements and the number of elements they can hold. Here's an example of of an integer array declaration and initialization:

int numbers[5] = {1, 2, 3, 4, 5};

In this example, numbers is an array of five integers, initialized with values from 1 to 5.

It is important to explicitely initialize arrays to ensure that they hold the intended values and to prevent undefined behavior.

Scope and default initialization

Things to remember about array initialization in C:

  • Size inference: if the size of the array is not specified, it is automatically determined based on the number of the elements. For example,
    int auto_size_array[] = {10, 20, 30};
    in this case, the size of the array is automatically set to 3.
  • Partial initialization: if fewer elements are provided than the array size, the remaining elements are initialized to zero.
    int fixed_size_array[5] = {1, 2};
    Here, the first two elements are set to 1 and 2, the rest is automatically set to 0.
  • Excess initializers: providing more elements than the declared size of the array results in a compilation error.
    int error_array[3] = {1, 2, 3, 4}; 
    This will result in an error.
  • Uninitialized local arrays: they are declared within a function (i.e., with local scope) and their values are not explicitely initialized. This means that their elements contain indeterminate (garbage) values, leading to undefined behavior if the values are accessed before being assigned.
    #include 
    int main(void) {
        int local_array[5]; // Uninitialized local array
        //code goes here
        return 0;
    }
    
  • Static arrays: they are eclared with the static keyword; their elements are automatically initialized to zero if not explicitly initialized.
    #include 
        int main(void) {
            static int static_array[5]; // Automatically initialized to zero
            //code goes here
            return 0;
        }

Dynamic array initialization

While static arrays have fixed sizes determined at compile time, dynamic arrays allow for flexibility by allocating memory at runtime using functions like malloc() and calloc(). Below is an example of dynamically allocating and initializing an array. Here, we use the sizeof operator to ensure that we are allocating the correct amount of memory, preventing buffer overflows and other memory-related issues.

#include <stdio.h>
#include <stdlib.h>

int main(void) {
    int size = 5;
    // Allocate memory for an array of 5 integers
    int *dynamic_array = malloc(size * sizeof(int));
    
    if(dynamic_array == NULL) {
        printf("Memory allocation failed.\n");
        return 1;
    }
    
    // Initialize the array
    for(int i = 0; i < size; i++) {
        dynamic_array[i] = i * 10;
    }
    
    // code goes here 
    
    // Free the allocated memory
    free(dynamic_array);
    
    return 0;
}

In this example, memory is allocated for an array of five integers at runtime. Each element is then initialized in a loop, and the allocated memory is freed using free().

3. Accessing array elements in C

In C, arrays use zero-based indexing. This means that the first element of an array is accessed with index 0, the second element with index 1, and so on.

Array elements can be accessed using their indices within square brackets []. The general syntax for accessing an element is:

array_name[index]

Where array_name is the name of the array and index is the position of the element you want to access.

#include <stdio.h>

int main(void) {
int numbers[5] = {10, 20, 30, 40, 50};

// Accessing array elements
printf("The first element: %d\n", numbers[0]); // Outputs: 10
printf("The third element: %d\n", numbers[2]); // Outputs: 30
printf("The last element: %d\n", numbers[4]);  // Outputs: 50

return 0;
}

4. Modifying array elements

The elements of an array can be modified by accessing them through their indices or pointers and assigning new values. Using the array index, you can directly update a specific element, such as array[2] = 50;, which changes the third element of the array to 50. Alternatively, pointer arithmetic allows you to modify elements by dereferencing a pointer to the array and navigating to the desired position, for example, *(ptr + 2) = 50;

#include <stdio.h>

int main(void) {
    int numbers[5] = {10, 20, 30, 40, 50};
    int *ptr = numbers; // Pointer to the first element
    
    printf("Before modification:\n");
    for(int i = 0; i < 5; i++) {
        printf("numbers[%d] = %d\n", i, numbers[i]);
    }
    
    // Modify the third element using pointer arithmetic
    *(ptr + 2) = 50; // Equivalent to numbers[2] = 50;
    
    printf("\nAfter modification:\n");
    for(int i = 0; i < 5; i++) {
        printf("numbers[%d] = %d\n", i, numbers[i]);
    }
    
    return 0;
}

Output:

Score of student 1: 85
Score of student 3: 75
Updated score of student 3: 80

In C, the name of an array acts as a pointer to its first element. Therefore, array_name is equivalent to &array_name[0]. This allows you to use pointers to access and manipulate array elements.

#include <stdio.h>

int main(void) {
    int arr[3] = {1, 2, 3};
    int *ptr = arr; // Equivalent to int *ptr = &arr[0];
    
    printf("First element using array: %d\n", arr[0]); // Outputs: 1
    printf("First element using pointer: %d\n", *ptr);  // Outputs: 1
    
    return 0;
}

5. Iterating through an array

Loops are commonly used to iterate through arrays, allowing you to access each element sequentially. Both for and while loops are suitable for this purpose.

Using a for loop

#include <stdio.h>

int main(void) {
    int numbers[5] = {10, 20, 30, 40, 50};
    
    // Iterating using a for loop
    for(int i = 0; i < 5; i++) {
        printf("numbers[%d] = %d\n", i, numbers[i]);
    }
    
    return 0;
}

Output:

numbers[0] = 10
numbers[1] = 20
numbers[2] = 30
numbers[3] = 40
numbers[4] = 50

Using a while loop with pointers

#include <stdio.h>

int main(void) {
    int numbers[5] = {5, 10, 15, 20, 25};
    int *ptr = numbers;
    int i = 0;
    
    // Iterating using a while loop and pointer arithmetic
    while(i < 5) {
        printf("numbers[%d] = %d\n", i, *(ptr + i));
        i++;
    }
    
    return 0;
}

Output:

numbers[0] = 5
numbers[1] = 10
numbers[2] = 15
numbers[3] = 20
numbers[4] = 25

Boundary considerations: preventing out-of-bounds access

Accessing array elements outside the valid range can lead to undefined behavior, including program crashes or security vulnerabilities. It's essential to ensure that all array accesses are within the bounds of the array.

#include <stdio.h>

int main(void) {
    int numbers[5] = {1, 2, 3, 4, 5};
    
    // Valid access
    printf("numbers[4] = %d\n", numbers[4]); // Outputs: 5
    
    // Invalid access (Out of bounds)
    printf("numbers[5] = %d\n", numbers[5]); // Undefined behavior
    
    return 0;
}
Note: C does not perform automatic bounds checking on array accesses. It's the programmer's responsibility to ensure that all indices are within the valid range.

Example of safe array access:

#include <stdio.h>

int main(void) {
    int numbers[5] = {10, 20, 30, 40, 50};
    int index;
    
    printf("Enter an index (0-4): ");
    fflush(stdout);
    scanf("%d", &index);
    
    if(index >= 0 && index < 5) {
        printf("numbers[%d] = %d\n", index, numbers[index]);
    } else {
        printf("Error: Index out of bounds.\n");
    }
    
    return 0;
}

Sample run:

Enter an index (0-4): 2
numbers[2] = 30

Sample run with out-of-bounds index:

Enter an index (0-4): 5
Error: Index out of bounds

6. Calculating the number of elements

Determining the number of elements in an array can be done using the sizeof operator. This approach is more reliable and flexible compared to hardcoding the size, especially when the array type might change.

The idiom sizeof(array) / sizeof(array[0]) calculates the number of elements by dividing the total size of the array by the size of its first element:

#include <stdio.h>
int main(void) {
    int numbers[] = {1, 2, 3, 4, 5};
    size_t count = sizeof(numbers) / sizeof(numbers[0]);
    printf("Number of elements: %zu\n", count); // Outputs: 5
    
    return 0;
}

Using this method ensures that the code remains correct even if the array type changes, for example, from int to double.

Scope and memory allocation

It's important to understand that sizeof will only return the size of an array when it refers to the original array name. When an array is passed to a function, it decays to a pointer, and sizeof will then return the size of the pointer, not the array.

#include <stdio.h>
#include <assert.h>

void count_elements(int days[], int len) {
    // sizeof(days) returns the size of a pointer, not the array
    assert(sizeof(days) / sizeof(days[0]) == len); // This will fail
}

int main(void) {
    int days[12] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11};
    count_elements(days, 12);
    return 0;
}

In the above code, sizeof(days) inside count_elements returns the size of the pointer, not the array, leading to incorrect calculations.

7. Character arrays

Character arrays in C have special properties due to their relationship with strings. While they can be initialized like other arrays, they also support initialization using string literals.

Character arrays can be initialized in two primary ways:

  • char letters[] = { 'a', 'b', 'c', 'd', 'e' };
  • char letters[] = "abcde";

When initialized using a string literal, C automatically appends a null terminator '\\0', making the array size one greater than the number of characters:

char letters[] = "abcde"; // Size is 6, including '\\0'
char letters_explicit[] = { 'a', 'b', 'c', 'd', 'e', '\\0' };

However, declaring an array with insufficient size for the string terminator is poor practice:

char letters[5] = "abcde"; // Correct but bad style. Size should be 6.
    

8. Arrays of pointers

Since pointers are variables themselves, they can be stored in arrays, enabling the creation of complex data structures like arrays of strings, arrays of function pointers, and more.

Array of pointers example

Consider an array of string literals:

#include <stdio.h>

int main(void) {
    char *months[] = { "January", "February", "March", "April" };
    for(int i = 0; i < 4; i++) {
        printf("Month %d: %s\n", i + 1, months[i]);
    }
    return 0;
}

Here, months is an array of pointers, each pointing to a string literal representing a month.

Function pointers in arrays

Function pointers can also be stored in arrays, allowing for dynamic function invocation based on indices. Here's an example of an array of function pointers used to perform arithmetic operations:

#include <stdio.h>
#include <assert.h>

// Arithmetic functions
double add(double a, double b) { return a + b; }
double subtract(double a, double b) { return a - b; }
double multiply(double a, double b) { return a * b; }
double divide(double a, double b) { 
    assert(b != 0.0); 
    return a / b; 
}

// Function to execute an operation
void execute_operation(double (*operation)(double, double), double x, double y) {
    double result = operation(x, y);
    printf("Result: %.2lf\n", result);
}

int main(void) {
    double val1 = 10.0, val2 = 5.0;
    double (*operations[])(double, double) = {add, subtract, multiply, divide};
    
    for(int i = 0; i < 4; i++) {
        execute_operation(operations[i], val1, val2);
    }
    
    return 0;
}

The program above defines four arithmetic functions: add, subtract, multiply, and divide. It then creates an array of function pointers, operations, each pointing to one of these functions. The execute_operation function takes a function pointer and two double values, invokes the pointed-to function with these values, and prints the result.

9. Multi-dimensional arrays

Multi-dimensional arrays allow for the storage of data in a grid-like structure. In C, multi-dimensional arrays are implemented as arrays of arrays.

9.1 Declaring multi-dimensional arrays

A two-dimensional array can be declared as follows:

#include <stdio.h>

int main(void) {
    int matrix[3][4] = {
        {1, 2, 3, 4},
        {5, 6, 7, 8},
        {9, 10, 11, 12}
    };
    
    // Accessing elements
    printf("Element at [1][2]: %d\n", matrix[1][2]); // Outputs: 7
    
    return 0;
}

Here, matrix is a 3x4 two-dimensional array. Internally, it is stored in row-major order, meaning all elements of the first row are stored first, followed by the second row, and so on.

matrix[0][0] Value: 1 matrix[0][3] Value: 4 matrix[2][0] Value: 9 matrix[2][3] Value: 12 Row 0 Row 2

9.2 Passing multi-dimensional arrays to functions

When passing multi-dimensional arrays to functions, all dimensions except the first must be specified. Here's an example of a function that multiplies two matrices:

#include <stdio.h>
#define SIZE 3

// Function to multiply two matrices
void multiply_matrices(int result[][SIZE], const int a[][SIZE], const int b[][SIZE]) {
    for(int i = 0; i < SIZE; i++) {
        for(int j = 0; j < SIZE; j++) {
            result[i][j] = 0;
            for(int k = 0; k < SIZE; k++) {
                result[i][j] += a[i][k] * b[k][j];
            }
        }
    }
}

int main(void) {
    int matrix1[SIZE][SIZE] = {
        {1, 2, 3},
        {4, 5, 6},
        {7, 8, 9}
    };
    
    int matrix2[SIZE][SIZE] = {
        {9, 8, 7},
        {6, 5, 4},
        {3, 2, 1}
    };
    
    int product[SIZE][SIZE];
    
    multiply_matrices(product, matrix1, matrix2);
    
    // Display the product matrix
    printf("Product Matrix:\n");
    for(int i = 0; i < SIZE; i++) {
        for(int j = 0; j < SIZE; j++) {
            printf("%d ", product[i][j]);
        }
        printf("\n");
    }
    
    return 0;
}

The multiply_matrices function takes three two-dimensional arrays as arguments: two input matrices and one result matrix. It performs matrix multiplication and stores the result in the result array.

9.3 Differences between multi-dimensional arrays and arrays of pointers

While both multi-dimensional arrays and arrays of pointers can be used to handle complex data structures, they differ in memory representation and flexibility:

  • Memory layout: multi-dimensional arrays are stored in contiguous memory blocks, whereas arrays of pointers can point to disparate memory locations.
  • Flexibility: arrays of pointers allow for rows of varying lengths, making them more flexible than multi-dimensional arrays which require uniform row sizes.
  • Syntax: accessing elements requires different syntaxes, which can be a source of confusion for beginners.

feature-top
Readers’ comment
feature-top
Log in to add a comment
🔐 Access