C Programming: Low-Level Mastery
HomeInsightsCoursesC ProgrammingDeclaring & Initializing Arrays
Arrays

Arrays Basics

Master C arrays - declaration, initialization, accessing elements, and bounds. Learn fixed-size collections, array limitations, and fundamental operations on sequential data.

What Are Arrays?

Arrays are collections of elements of the same type stored in contiguous memory locations. They provide indexed access to elements - you can get to any element in constant time using its index. Arrays are C's fundamental data structure for storing lists, tables, buffers, and any collection of homogeneous data.

Unlike variables that hold single values, arrays hold multiple values accessible through integer indices starting at 0. The first element is arr[0], the second is arr[1], and so on. Arrays have fixed size determined at declaration - they cannot grow or shrink. This simplicity makes arrays fast but requires careful size management.

C
#include <stdio.h>

int main(void) {
    /* Array declaration */
    int numbers[5];  // Array of 5 integers
    
    /* Assigning values */
    numbers[0] = 10;
    numbers[1] = 20;
    numbers[2] = 30;
    numbers[3] = 40;
    numbers[4] = 50;
    
    /* Accessing elements */
    printf("First element: %d\n", numbers[0]);  // 10
    printf("Third element: %d\n", numbers[2]);  // 30
    printf("Last element: %d\n", numbers[4]);   // 50
    
    /* Modify elements */
    numbers[2] = 99;
    printf("Modified third: %d\n", numbers[2]);  // 99
    
    return 0;
}

Array Key Points:

  • Elements are same type
  • Stored in contiguous memory
  • Index starts at 0
  • Fixed size (cannot change)
  • Fast random access O(1)
  • No bounds checking!

Array Declaration and Initialization

C provides several ways to declare and initialize arrays. You can declare without initialization (elements contain garbage for local arrays, zeros for global/static), initialize with explicit values, or let the compiler determine size from initializer. Understanding initialization rules prevents bugs from uninitialized data.

C
/* Declaration without initialization */
int arr1[10];  // 10 integers, values are garbage (local array)

/* Declaration with initialization */
int arr2[5] = {1, 2, 3, 4, 5};  // All elements specified

/* Partial initialization */
int arr3[5] = {1, 2};  // {1, 2, 0, 0, 0} - remaining are 0

/* Let compiler determine size */
int arr4[] = {10, 20, 30};  // Size is 3 (inferred)

/* Initialize all to zero */
int arr5[100] = {0};  // All elements are 0

/* Initialize all to same non-zero value (C99+) */
int arr6[5] = {[0 ... 4] = 7};  // GCC extension: all 5 elements are 7

/* Character arrays (strings) */
char str1[6] = {'H', 'e', 'l', 'l', 'o', '\0'};
char str2[] = "Hello";  // Automatically adds \0, size is 6

/* 2D array initialization */
int matrix[3][3] = {
    {1, 2, 3},
    {4, 5, 6},
    {7, 8, 9}
};

/* Global vs local initialization */
int global_arr[5];  // Automatically initialized to {0, 0, 0, 0, 0}

void function(void) {
    int local_arr[5];  // Contains garbage! Must initialize explicitly
    
    /* Explicitly initialize */
    int safe_arr[5] = {0};  // All zeros
}

Accessing Array Elements

Array elements are accessed using the subscript operator []. The index must be between 0 and size-1. C doesn't check array bounds - accessing outside the array causes undefined behavior, often leading to crashes or security vulnerabilities. Always ensure indices are valid.

C
#include <stdio.h>

int main(void) {
    int numbers[5] = {10, 20, 30, 40, 50};
    
    /* Valid access */
    printf("%d\n", numbers[0]);  // 10 (first element)
    printf("%d\n", numbers[4]);  // 50 (last element)
    
    /* Loop through array */
    for (int i = 0; i < 5; i++) {
        printf("%d ", numbers[i]);
    }
    printf("\n");  // Output: 10 20 30 40 50
    
    /* Calculate size */
    int size = sizeof(numbers) / sizeof(numbers[0]);
    printf("Array size: %d\n", size);  // 5
    
    /* Modify elements */
    for (int i = 0; i < size; i++) {
        numbers[i] *= 2;  // Double each element
    }
    
    /* DANGEROUS: Out of bounds access */
    // printf("%d\n", numbers[10]);  // WRONG: Undefined behavior
    // numbers[10] = 99;  // WRONG: Buffer overflow!
    
    /* Safe access with bounds checking */
    int index = 10;
    if (index >= 0 && index < size) {
        printf("%d\n", numbers[index]);
    } else {
        printf("Index out of bounds!\n");
    }
    
    return 0;
}

/* Common pattern: Pass array with size */
void print_array(int arr[], int size) {
    for (int i = 0; i < size; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");
}

/* Using array in function */
void process(void) {
    int data[] = {1, 2, 3, 4, 5};
    int size = sizeof(data) / sizeof(data[0]);
    print_array(data, size);
}

Common Array Operations

Arrays support common operations like searching, finding minimum/maximum, summing elements, and reversing. These patterns appear frequently in C programming and demonstrate how to work with arrays effectively.

C
/* Sum of array elements */
int sum_array(int arr[], int size) {
    int sum = 0;
    for (int i = 0; i < size; i++) {
        sum += arr[i];
    }
    return sum;
}

/* Find maximum element */
int find_max(int arr[], int size) {
    if (size == 0) return 0;  // Handle empty array
    
    int max = arr[0];
    for (int i = 1; i < size; i++) {
        if (arr[i] &gt; max) {
            max = arr[i];
        }
    }
    return max;
}

/* Find minimum element */
int find_min(int arr[], int size) {
    if (size == 0) return 0;
    
    int min = arr[0];
    for (int i = 1; i < size; i++) {
        if (arr[i] < min) {
            min = arr[i];
        }
    }
    return min;
}

/* Search for element (linear search) */
int search(int arr[], int size, int target) {
    for (int i = 0; i < size; i++) {
        if (arr[i] == target) {
            return i;  // Return index where found
        }
    }
    return -1;  // Not found
}

/* Reverse array */
void reverse_array(int arr[], int size) {
    for (int i = 0; i < size / 2; i++) {
        /* Swap elements from ends */
        int temp = arr[i];
        arr[i] = arr[size - 1 - i];
        arr[size - 1 - i] = temp;
    }
}

/* Copy array */
void copy_array(int dest[], const int src[], int size) {
    for (int i = 0; i < size; i++) {
        dest[i] = src[i];
    }
}

/* Check if arrays are equal */
int arrays_equal(int arr1[], int arr2[], int size) {
    for (int i = 0; i < size; i++) {
        if (arr1[i] != arr2[i]) {
            return 0;  // Not equal
        }
    }
    return 1;  // Equal
}

/* Usage example */
int main(void) {
    int numbers[] = {5, 2, 8, 1, 9, 3, 7};
    int size = sizeof(numbers) / sizeof(numbers[0]);
    
    printf("Sum: %d\n", sum_array(numbers, size));
    printf("Max: %d\n", find_max(numbers, size));
    printf("Min: %d\n", find_min(numbers, size));
    
    int index = search(numbers, size, 9);
    if (index != -1) {
        printf("Found 9 at index %d\n", index);
    }
    
    reverse_array(numbers, size);
    printf("Reversed: ");
    for (int i = 0; i < size; i++) {
        printf("%d ", numbers[i]);
    }
    printf("\n");
    
    return 0;
}

Multidimensional Arrays

C supports multidimensional arrays - arrays of arrays. The most common is 2D arrays (matrices), but you can have 3D, 4D, etc. Multidimensional arrays are stored in row-major order in memory. Understanding their layout helps with performance and debugging.

C
/* 2D array declaration */
int matrix[3][4];  // 3 rows, 4 columns

/* 2D array initialization */
int grid[3][3] = {
    {1, 2, 3},
    {4, 5, 6},
    {7, 8, 9}
};

/* Alternative initialization */
int flat[3][3] = {1, 2, 3, 4, 5, 6, 7, 8, 9};  // Same result

/* Accessing 2D array elements */
printf("%d\n", grid[0][0]);  // 1 (first element)
printf("%d\n", grid[1][2]);  // 6 (row 1, column 2)
printf("%d\n", grid[2][2]);  // 9 (last element)

/* Iterating 2D array */
void print_matrix(int arr[][3], int rows) {  // Column size required!
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < 3; j++) {
            printf("%d ", arr[i][j]);
        }
        printf("\n");
    }
}

/* Matrix operations */

/* Initialize 2D array */
void init_matrix(int arr[][4], int rows, int cols, int value) {
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < cols; j++) {
            arr[i][j] = value;
        }
    }
}

/* Sum of all elements in 2D array */
int sum_matrix(int arr[][3], int rows) {
    int sum = 0;
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < 3; j++) {
            sum += arr[i][j];
        }
    }
    return sum;
}

/* Transpose matrix */
void transpose(int src[][3], int dest[][3], int size) {
    for (int i = 0; i < size; i++) {
        for (int j = 0; j < size; j++) {
            dest[j][i] = src[i][j];
        }
    }
}

/* 3D array */
int cube[2][3][4];  // 2 layers, 3 rows, 4 columns per layer

/* Accessing 3D array */
cube[0][1][2] = 42;
printf("%d\n", cube[0][1][2]);

/* Memory layout (row-major) */
int arr[2][3] = {{1,2,3}, {4,5,6}};
// Memory: [1][2][3][4][5][6]
// Contiguous storage, row by row

Arrays and Functions

When you pass an array to a function, you're actually passing a pointer to the first element. Arrays "decay" to pointers. This means functions can modify the original array, and sizeof doesn't work on array parameters. Always pass the size separately to functions.

C
/* Array as function parameter */
void modify_array(int arr[], int size) {
    // arr is actually a pointer to first element
    // Cannot use sizeof(arr) here!
    
    for (int i = 0; i < size; i++) {
        arr[i] *= 2;  // Modifies original array
    }
}

/* Equivalent declarations */
void func1(int arr[], int size);
void func2(int *arr, int size);  // Same thing!

/* const array parameter (read-only) */
void print_array(const int arr[], int size) {
    for (int i = 0; i < size; i++) {
        printf("%d ", arr[i]);
        // arr[i] = 0;  // ERROR: Can't modify const array
    }
    printf("\n");
}

/* 2D array parameter */
void process_matrix(int arr[][4], int rows) {
    // Must specify column size!
    // Cannot use int arr[][]
    
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < 4; j++) {
            arr[i][j] += 10;
        }
    }
}

/* Returning array (NOT possible directly) */
// int[] get_array(void);  // ERROR: Can't return array

/* Workaround 1: Return pointer to static array */
int* get_static_array(void) {
    static int arr[5] = {1, 2, 3, 4, 5};
    return arr;  // OK: static array persists
}

/* Workaround 2: Modify array passed by caller */
void fill_array(int arr[], int size, int value) {
    for (int i = 0; i < size; i++) {
        arr[i] = value;
    }
}

/* Usage */
int main(void) {
    int numbers[5] = {1, 2, 3, 4, 5};
    
    printf("Before: ");
    print_array(numbers, 5);
    
    modify_array(numbers, 5);
    
    printf("After: ");
    print_array(numbers, 5);  // Values are doubled
    
    return 0;
}

Common Pitfalls

Arrays in C have several gotchas that catch even experienced programmers. Understanding these helps you avoid crashes, security vulnerabilities, and subtle bugs.

C
/* Pitfall 1: Buffer overflow (no bounds checking) */
int arr[5] = {1, 2, 3, 4, 5};
arr[10] = 99;  // WRONG: Undefined behavior, may crash

/* Pitfall 2: sizeof on array parameter */
void wrong_size(int arr[]) {
    int size = sizeof(arr) / sizeof(arr[0]);  // WRONG!
    // sizeof(arr) is sizeof(int*), not array size
    printf("Wrong size: %zu\n", sizeof(arr));  // 8 (pointer size)
}

/* Correct: Pass size separately */
void correct_size(int arr[], int size) {
    for (int i = 0; i < size; i++) {
        printf("%d ", arr[i]);
    }
}

/* Pitfall 3: Uninitialized local arrays */
void uninitialized(void) {
    int arr[5];  // Contains garbage!
    printf("%d\n", arr[0]);  // Undefined behavior
}

/* Correct: Initialize */
void initialized(void) {
    int arr[5] = {0};  // All zeros
}

/* Pitfall 4: Array assignment not allowed */
int arr1[5] = {1, 2, 3, 4, 5};
int arr2[5];
// arr2 = arr1;  // ERROR: Can't assign arrays

/* Correct: Copy element by element */
for (int i = 0; i < 5; i++) {
    arr2[i] = arr1[i];
}
// Or use memcpy:
memcpy(arr2, arr1, sizeof(arr1));

/* Pitfall 5: Returning local array */
int* bad_return(void) {
    int arr[5] = {1, 2, 3, 4, 5};
    return arr;  // WRONG: arr destroyed after return
}

/* Pitfall 6: VLA (Variable Length Array) issues */
void vla_issues(int n) {
    int arr[n];  // VLA - supported in C99 but optional in C11
    // Can cause stack overflow for large n
    // Not all compilers support VLAs
}

/* Pitfall 7: Negative or huge indices */
int arr[5];
arr[-1] = 10;  // WRONG: Negative index
arr[1000000] = 10;  // WRONG: Way out of bounds

/* Best Practice: Always validate indices */
int safe_access(int arr[], int size, int index, int *value) {
    if (index < 0 || index >= size) {
        return 0;  // Error
    }
    *value = arr[index];
    return 1;  // Success
}

Summary & What's Next

Key Takeaways:

  • ✅ Arrays store multiple elements of same type
  • ✅ Indices start at 0, last element is size-1
  • ✅ Fixed size, cannot grow or shrink
  • ✅ No automatic bounds checking - programmer's responsibility
  • ✅ Initialize with = {values} or = {0} for all zeros
  • ✅ Arrays decay to pointers when passed to functions
  • ✅ Always pass size separately to functions
  • ✅ Cannot assign arrays directly, must copy elements

What's Next?

Let's learn about multidimensional arrays and advanced array techniques!