Arrays are fundamental data structures that form the backbone of countless C programs. They provide a way to store and manipulate collections of data efficiently, enabling everything from simple lists to complex matrix operations. This comprehensive guide explores arrays in C from basic concepts to advanced techniques, with practical examples and performance considerations.
What is an Array?
An array is a contiguous block of memory that stores multiple elements of the same data type. Elements are accessed using an index, with the first element at index 0.
Memory Layout: Address: 1000 1004 1008 1012 1016 +------+------+------+------+------+ int arr[5]:| 10 | 20 | 30 | 40 | 50 | +------+------+------+------+------+ Index: 0 1 2 3 4
Array Declaration and Initialization
1. Basic Declaration
#include <stdio.h>
void basic_declaration() {
// Declaration without initialization
int numbers[10]; // Array of 10 integers (uninitialized)
float prices[20]; // Array of 20 floats
char name[50]; // Array of 50 characters
// Declaration with initialization
int scores[5] = {85, 90, 78, 92, 88};
int values[] = {1, 2, 3, 4, 5}; // Size inferred from initializer
// Partial initialization (remaining elements set to 0)
int partial[10] = {1, 2, 3}; // First 3 elements: 1,2,3; remaining: 0
// Designated initializers (C99)
int designated[10] = {[0] = 10, [5] = 20, [9] = 30};
// All elements to zero
int zero[100] = {0};
}
2. Character Arrays and Strings
#include <stdio.h>
#include <string.h>
void string_arrays() {
// Character array (not necessarily a string)
char chars[5] = {'H', 'e', 'l', 'l', 'o'}; // No null terminator
// String (null-terminated character array)
char str1[] = "Hello"; // Size 6 (includes '\0')
char str2[10] = "World"; // Size 10, contains "World\0"
char str3[6] = {'H', 'e', 'l', 'l', 'o', '\0'};
// String operations
printf("Length: %zu\n", strlen(str1));
printf("Size: %zu\n", sizeof(str1));
// String input
char buffer[100];
printf("Enter text: ");
fgets(buffer, sizeof(buffer), stdin);
buffer[strcspn(buffer, "\n")] = '\0'; // Remove newline
}
Array Access and Manipulation
1. Basic Access
#include <stdio.h>
void array_access() {
int arr[5] = {10, 20, 30, 40, 50};
// Accessing elements
printf("First element: %d\n", arr[0]); // 10
printf("Third element: %d\n", arr[2]); // 30
// Modifying elements
arr[1] = 25;
arr[4] = 55;
// Array bounds - BE CAREFUL!
// arr[5] = 60; // Undefined behavior! Out of bounds
// Reading elements
for (int i = 0; i < 5; i++) {
printf("arr[%d] = %d\n", i, arr[i]);
}
}
2. Input/Output with Arrays
#include <stdio.h>
void array_io() {
int scores[5];
// Reading into array
printf("Enter 5 scores:\n");
for (int i = 0; i < 5; i++) {
printf("Score %d: ", i + 1);
scanf("%d", &scores[i]);
}
// Displaying array
printf("\nScores: ");
for (int i = 0; i < 5; i++) {
printf("%d ", scores[i]);
}
printf("\n");
// Calculate average
int sum = 0;
for (int i = 0; i < 5; i++) {
sum += scores[i];
}
printf("Average: %.2f\n", (float)sum / 5);
}
Multidimensional Arrays
1. Two-Dimensional Arrays
#include <stdio.h>
void two_dimensional_arrays() {
// Declaration
int matrix[3][4]; // 3 rows, 4 columns
// Initialization
int grid[3][4] = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}
};
// Accessing elements
printf("Element at (1,2): %d\n", grid[1][2]); // 7
// Modifying elements
grid[0][0] = 100;
// Row-major traversal
printf("Row-major order:\n");
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 4; j++) {
printf("%3d ", grid[i][j]);
}
printf("\n");
}
// Column-major traversal (less efficient in C)
printf("\nColumn-major order:\n");
for (int j = 0; j < 4; j++) {
for (int i = 0; i < 3; i++) {
printf("%3d ", grid[i][j]);
}
printf("\n");
}
}
2. Three-Dimensional Arrays
#include <stdio.h>
void three_dimensional_arrays() {
// 3D array: 2 layers, 3 rows, 4 columns
int cube[2][3][4] = {
{ // Layer 0
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}
},
{ // Layer 1
{13, 14, 15, 16},
{17, 18, 19, 20},
{21, 22, 23, 24}
}
};
// Access
printf("Element at (1,2,3): %d\n", cube[1][2][3]); // 24
// Traversal
for (int l = 0; l < 2; l++) {
printf("Layer %d:\n", l);
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 4; j++) {
printf("%3d ", cube[l][i][j]);
}
printf("\n");
}
printf("\n");
}
}
3. Variable Length Arrays (VLA) - C99
#include <stdio.h>
void variable_length_arrays(int rows, int cols) {
// VLA - size determined at runtime
int matrix[rows][cols];
// Initialize
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
matrix[i][j] = i * cols + j;
}
}
// Display
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
printf("%3d ", matrix[i][j]);
}
printf("\n");
}
}
Arrays and Pointers
The relationship between arrays and pointers is fundamental to C programming.
#include <stdio.h>
void array_pointer_relationship() {
int arr[5] = {10, 20, 30, 40, 50};
// Array name decays to pointer to first element
int *ptr = arr; // Equivalent to &arr[0]
// Pointer arithmetic
printf("arr[0] = %d, *ptr = %d\n", arr[0], *ptr);
printf("arr[1] = %d, *(ptr+1) = %d\n", arr[1], *(ptr + 1));
// Array indexing is syntactic sugar for pointer arithmetic
// arr[i] is equivalent to *(arr + i)
for (int i = 0; i < 5; i++) {
printf("arr[%d] = %d, *(arr+%d) = %d\n",
i, arr[i], i, *(arr + i));
}
// Pointer to array (vs array of pointers)
int (*ptr_to_array)[5] = &arr; // Pointer to array of 5 ints
int *array_of_ptrs[5]; // Array of 5 int pointers
// sizeof behavior
printf("sizeof(arr): %zu\n", sizeof(arr)); // 20 (5*4)
printf("sizeof(ptr): %zu\n", sizeof(ptr)); // 8 (pointer size)
}
Arrays in Functions
1. Passing Arrays to Functions
#include <stdio.h>
// Arrays decay to pointers when passed to functions
void print_array(int arr[], int size) {
// arr is actually a pointer
printf("Size of arr in function: %zu\n", sizeof(arr)); // Pointer size, not array size
for (int i = 0; i < size; i++) {
printf("%d ", arr[i]);
}
printf("\n");
}
// Equivalent using pointer syntax
void print_array_pointer(int *arr, int size) {
for (int i = 0; i < size; i++) {
printf("%d ", arr[i]);
}
printf("\n");
}
// Modify array in function
void double_array(int arr[], int size) {
for (int i = 0; i < size; i++) {
arr[i] *= 2;
}
}
// Passing multidimensional arrays
void print_matrix(int rows, int cols, int matrix[][cols]) {
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
printf("%3d ", matrix[i][j]);
}
printf("\n");
}
}
// Using pointers for multidimensional arrays
void print_matrix_pointer(int rows, int cols, int (*matrix)[cols]) {
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
printf("%3d ", matrix[i][j]);
}
printf("\n");
}
}
int main() {
int arr[5] = {1, 2, 3, 4, 5};
print_array(arr, 5);
double_array(arr, 5);
print_array(arr, 5);
int matrix[3][4] = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}
};
print_matrix(3, 4, matrix);
return 0;
}
2. Returning Arrays from Functions
#include <stdio.h>
#include <stdlib.h>
// Return pointer to static array (careful - not thread-safe)
int* get_static_array() {
static int arr[5] = {1, 2, 3, 4, 5};
return arr;
}
// Return dynamically allocated array (caller must free)
int* create_dynamic_array(int size) {
int *arr = (int*)malloc(size * sizeof(int));
if (arr == NULL) {
return NULL;
}
for (int i = 0; i < size; i++) {
arr[i] = i * 10;
}
return arr;
}
// Return array via parameter (common pattern)
void create_array_via_param(int **arr, int size) {
*arr = (int*)malloc(size * sizeof(int));
if (*arr == NULL) {
return;
}
for (int i = 0; i < size; i++) {
(*arr)[i] = i * 10;
}
}
int main() {
// Static array
int *static_arr = get_static_array();
printf("Static array: ");
for (int i = 0; i < 5; i++) {
printf("%d ", static_arr[i]);
}
printf("\n");
// Dynamic array
int *dynamic_arr = create_dynamic_array(10);
if (dynamic_arr) {
printf("Dynamic array: ");
for (int i = 0; i < 10; i++) {
printf("%d ", dynamic_arr[i]);
}
printf("\n");
free(dynamic_arr);
}
return 0;
}
Common Array Algorithms
1. Searching
#include <stdio.h>
#include <stdbool.h>
// Linear search
int linear_search(int arr[], int size, int target) {
for (int i = 0; i < size; i++) {
if (arr[i] == target) {
return i; // Return index
}
}
return -1; // Not found
}
// Binary search (requires sorted array)
int binary_search(int arr[], int size, int target) {
int left = 0;
int right = size - 1;
while (left <= right) {
int mid = left + (right - left) / 2; // Avoid overflow
if (arr[mid] == target) {
return mid;
} else if (arr[mid] < target) {
left = mid + 1;
} else {
right = mid - 1;
}
}
return -1;
}
void search_demo() {
int arr[] = {10, 20, 30, 40, 50, 60, 70};
int size = sizeof(arr) / sizeof(arr[0]);
int index = linear_search(arr, size, 40);
printf("Linear search: 40 found at index %d\n", index);
index = binary_search(arr, size, 40);
printf("Binary search: 40 found at index %d\n", index);
index = binary_search(arr, size, 100);
printf("Binary search: 100 not found (index %d)\n", index);
}
2. Sorting
#include <stdio.h>
// Bubble sort
void bubble_sort(int arr[], int size) {
for (int i = 0; i < size - 1; i++) {
for (int j = 0; j < size - i - 1; j++) {
if (arr[j] > arr[j + 1]) {
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
}
// Selection sort
void selection_sort(int arr[], int size) {
for (int i = 0; i < size - 1; i++) {
int min_idx = i;
for (int j = i + 1; j < size; j++) {
if (arr[j] < arr[min_idx]) {
min_idx = j;
}
}
if (min_idx != i) {
int temp = arr[i];
arr[i] = arr[min_idx];
arr[min_idx] = temp;
}
}
}
// Insertion sort (efficient for small arrays)
void insertion_sort(int arr[], int size) {
for (int i = 1; i < size; i++) {
int key = arr[i];
int j = i - 1;
while (j >= 0 && arr[j] > key) {
arr[j + 1] = arr[j];
j--;
}
arr[j + 1] = key;
}
}
// Quick sort (recursive)
void quick_sort(int arr[], int low, int high) {
if (low < high) {
int pivot = arr[high];
int i = low - 1;
for (int j = low; j < high; j++) {
if (arr[j] <= pivot) {
i++;
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
int temp = arr[i + 1];
arr[i + 1] = arr[high];
arr[high] = temp;
int pi = i + 1;
quick_sort(arr, low, pi - 1);
quick_sort(arr, pi + 1, high);
}
}
void sort_demo() {
int arr[] = {64, 34, 25, 12, 22, 11, 90};
int size = sizeof(arr) / sizeof(arr[0]);
printf("Original: ");
for (int i = 0; i < size; i++) printf("%d ", arr[i]);
printf("\n");
bubble_sort(arr, size);
printf("Bubble sorted: ");
for (int i = 0; i < size; i++) printf("%d ", arr[i]);
printf("\n");
}
3. Array Operations
#include <stdio.h>
#include <string.h>
// Reverse array
void reverse_array(int arr[], int size) {
for (int i = 0; i < size / 2; i++) {
int temp = arr[i];
arr[i] = arr[size - 1 - i];
arr[size - 1 - i] = temp;
}
}
// Rotate array left by k positions
void rotate_left(int arr[], int size, int k) {
k = k % size;
if (k == 0) return;
// Temporary array for first k elements
int temp[k];
for (int i = 0; i < k; i++) {
temp[i] = arr[i];
}
// Shift remaining elements left
for (int i = k; i < size; i++) {
arr[i - k] = arr[i];
}
// Copy temp to end
for (int i = 0; i < k; i++) {
arr[size - k + i] = temp[i];
}
}
// Rotate array right by k positions
void rotate_right(int arr[], int size, int k) {
k = k % size;
if (k == 0) return;
// Temporary array for last k elements
int temp[k];
for (int i = 0; i < k; i++) {
temp[i] = arr[size - k + i];
}
// Shift remaining elements right
for (int i = size - 1; i >= k; i--) {
arr[i] = arr[i - k];
}
// Copy temp to beginning
for (int i = 0; i < k; i++) {
arr[i] = temp[i];
}
}
// Remove duplicates from sorted array
int remove_duplicates(int arr[], int size) {
if (size == 0) return 0;
int j = 0;
for (int i = 1; i < size; i++) {
if (arr[i] != arr[j]) {
j++;
arr[j] = arr[i];
}
}
return j + 1; // New size
}
// Merge two sorted arrays
void merge_sorted(int arr1[], int size1, int arr2[], int size2, int result[]) {
int i = 0, j = 0, k = 0;
while (i < size1 && j < size2) {
if (arr1[i] < arr2[j]) {
result[k++] = arr1[i++];
} else {
result[k++] = arr2[j++];
}
}
while (i < size1) {
result[k++] = arr1[i++];
}
while (j < size2) {
result[k++] = arr2[j++];
}
}
void array_operations_demo() {
int arr[] = {1, 2, 3, 4, 5, 6, 7};
int size = sizeof(arr) / sizeof(arr[0]);
printf("Original: ");
for (int i = 0; i < size; i++) printf("%d ", arr[i]);
printf("\n");
reverse_array(arr, size);
printf("Reversed: ");
for (int i = 0; i < size; i++) printf("%d ", arr[i]);
printf("\n");
rotate_left(arr, size, 2);
printf("Rotate left by 2: ");
for (int i = 0; i < size; i++) printf("%d ", arr[i]);
printf("\n");
int sorted[] = {1, 2, 2, 3, 4, 4, 4, 5};
int sorted_size = sizeof(sorted) / sizeof(sorted[0]);
int new_size = remove_duplicates(sorted, sorted_size);
printf("After removing duplicates: ");
for (int i = 0; i < new_size; i++) printf("%d ", sorted[i]);
printf("\n");
}
Dynamic Arrays (Resizable Arrays)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct {
int *data;
size_t size;
size_t capacity;
} DynamicArray;
// Initialize dynamic array
DynamicArray* da_create(size_t initial_capacity) {
DynamicArray *arr = (DynamicArray*)malloc(sizeof(DynamicArray));
if (arr == NULL) return NULL;
arr->data = (int*)malloc(initial_capacity * sizeof(int));
if (arr->data == NULL) {
free(arr);
return NULL;
}
arr->size = 0;
arr->capacity = initial_capacity;
return arr;
}
// Resize array
int da_resize(DynamicArray *arr, size_t new_capacity) {
int *new_data = (int*)realloc(arr->data, new_capacity * sizeof(int));
if (new_data == NULL) return -1;
arr->data = new_data;
arr->capacity = new_capacity;
return 0;
}
// Append element
int da_append(DynamicArray *arr, int value) {
if (arr->size >= arr->capacity) {
size_t new_capacity = arr->capacity * 2;
if (da_resize(arr, new_capacity) != 0) return -1;
}
arr->data[arr->size++] = value;
return 0;
}
// Insert at index
int da_insert(DynamicArray *arr, size_t index, int value) {
if (index > arr->size) return -1;
if (arr->size >= arr->capacity) {
size_t new_capacity = arr->capacity * 2;
if (da_resize(arr, new_capacity) != 0) return -1;
}
// Shift elements right
for (size_t i = arr->size; i > index; i--) {
arr->data[i] = arr->data[i - 1];
}
arr->data[index] = value;
arr->size++;
return 0;
}
// Remove at index
int da_remove(DynamicArray *arr, size_t index) {
if (index >= arr->size) return -1;
// Shift elements left
for (size_t i = index; i < arr->size - 1; i++) {
arr->data[i] = arr->data[i + 1];
}
arr->size--;
// Shrink if too small
if (arr->size > 0 && arr->size <= arr->capacity / 4) {
size_t new_capacity = arr->capacity / 2;
da_resize(arr, new_capacity);
}
return 0;
}
// Get element
int da_get(DynamicArray *arr, size_t index, int *value) {
if (index >= arr->size) return -1;
*value = arr->data[index];
return 0;
}
// Set element
int da_set(DynamicArray *arr, size_t index, int value) {
if (index >= arr->size) return -1;
arr->data[index] = value;
return 0;
}
// Free array
void da_free(DynamicArray *arr) {
if (arr) {
free(arr->data);
free(arr);
}
}
void dynamic_array_demo() {
DynamicArray *arr = da_create(4);
// Append elements
for (int i = 0; i < 10; i++) {
da_append(arr, i * 10);
}
printf("Dynamic array: ");
for (size_t i = 0; i < arr->size; i++) {
printf("%d ", arr->data[i]);
}
printf("\n");
printf("Size: %zu, Capacity: %zu\n", arr->size, arr->capacity);
// Insert
da_insert(arr, 3, 999);
printf("After insert at index 3: ");
for (size_t i = 0; i < arr->size; i++) {
printf("%d ", arr->data[i]);
}
printf("\n");
// Remove
da_remove(arr, 5);
printf("After remove at index 5: ");
for (size_t i = 0; i < arr->size; i++) {
printf("%d ", arr->data[i]);
}
printf("\n");
da_free(arr);
}
Common Array Idioms
1. Finding Min/Max
#include <limits.h>
void find_min_max(int arr[], int size, int *min, int *max) {
*min = INT_MAX;
*max = INT_MIN;
for (int i = 0; i < size; i++) {
if (arr[i] < *min) *min = arr[i];
if (arr[i] > *max) *max = arr[i];
}
}
void find_second_largest(int arr[], int size, int *second) {
int first = INT_MIN;
*second = INT_MIN;
for (int i = 0; i < size; i++) {
if (arr[i] > first) {
*second = first;
first = arr[i];
} else if (arr[i] > *second && arr[i] != first) {
*second = arr[i];
}
}
}
2. Array Statistics
double array_mean(int arr[], int size) {
if (size == 0) return 0;
long long sum = 0;
for (int i = 0; i < size; i++) {
sum += arr[i];
}
return (double)sum / size;
}
double array_median(int arr[], int size) {
if (size == 0) return 0;
// Need sorted array
int temp[size];
memcpy(temp, arr, size * sizeof(int));
insertion_sort(temp, size);
if (size % 2 == 0) {
return (temp[size/2 - 1] + temp[size/2]) / 2.0;
} else {
return temp[size/2];
}
}
int array_mode(int arr[], int size) {
// Assumes positive integers, range limited
int max_val = 0;
for (int i = 0; i < size; i++) {
if (arr[i] > max_val) max_val = arr[i];
}
int *freq = (int*)calloc(max_val + 1, sizeof(int));
for (int i = 0; i < size; i++) {
freq[arr[i]]++;
}
int mode = 0;
int max_freq = 0;
for (int i = 0; i <= max_val; i++) {
if (freq[i] > max_freq) {
max_freq = freq[i];
mode = i;
}
}
free(freq);
return mode;
}
Performance Considerations
#include <stdio.h>
#include <time.h>
// Cache-friendly traversal (row-major)
void cache_friendly(int matrix[][1000], int size) {
long long sum = 0;
for (int i = 0; i < size; i++) {
for (int j = 0; j < size; j++) {
sum += matrix[i][j]; // Sequential memory access
}
}
}
// Cache-unfriendly traversal (column-major)
void cache_unfriendly(int matrix[][1000], int size) {
long long sum = 0;
for (int j = 0; j < size; j++) {
for (int i = 0; i < size; i++) {
sum += matrix[i][j]; // Strided memory access
}
}
}
void performance_demo() {
const int SIZE = 1000;
int matrix[SIZE][SIZE];
// Initialize
for (int i = 0; i < SIZE; i++) {
for (int j = 0; j < SIZE; j++) {
matrix[i][j] = i + j;
}
}
clock_t start = clock();
cache_friendly(matrix, SIZE);
clock_t end = clock();
printf("Cache-friendly: %.3f seconds\n",
(double)(end - start) / CLOCKS_PER_SEC);
start = clock();
cache_unfriendly(matrix, SIZE);
end = clock();
printf("Cache-unfriendly: %.3f seconds\n",
(double)(end - start) / CLOCKS_PER_SEC);
}
Best Practices Summary
- Always check bounds: Never access array beyond its size
- Use sizeof for size:
sizeof(arr) / sizeof(arr[0])for static arrays - Pass size to functions: Arrays decay to pointers, so pass size explicitly
- Initialize arrays: Uninitialized arrays contain garbage values
- Be careful with strings: Ensure null termination
- Use const for read-only arrays:
const int arr[] - Consider stack limits: Large arrays may cause stack overflow; use dynamic allocation
- Optimize for cache: Access memory sequentially when possible
- Document array parameters: Indicate whether function modifies the array
- Use flexible array members: For variable-sized structures
Common Pitfalls
void common_pitfalls() {
// Pitfall 1: Off-by-one errors
int arr[5];
for (int i = 0; i <= 5; i++) { // Accesses arr[5] - out of bounds!
arr[i] = i;
}
// Pitfall 2: Assuming sizeof on pointer gives array size
void func(int arr[]) {
// sizeof(arr) is sizeof(int*), not array size!
}
// Pitfall 3: Returning local array
int* bad_return() {
int local[10]; // Local array
return local; // Returns pointer to local (undefined behavior)
}
// Pitfall 4: Forgetting null terminator
char str[3] = {'a', 'b', 'c'}; // Not null-terminated!
// printf("%s", str); // Undefined behavior
// Pitfall 5: Using uninitialized array elements
int uninitialized[10];
int sum = 0;
for (int i = 0; i < 10; i++) {
sum += uninitialized[i]; // Garbage values
}
}
Conclusion
Arrays are fundamental to C programming, providing efficient, contiguous storage for collections of data. Mastering arrays involves understanding:
- Declaration, initialization, and access patterns
- The relationship between arrays and pointers
- Passing arrays to functions (and the decay to pointers)
- Multidimensional arrays and memory layout
- Common algorithms and performance considerations
- Dynamic array implementation for resizable collections
With the patterns and techniques covered in this guide, you can confidently use arrays to build efficient and robust C programs. Remember that arrays are the foundation for many advanced data structures and algorithms—a solid understanding of arrays will serve you throughout your C programming journey.