What Are Arrays
Arrays are fundamental data structures in C that allow you to store multiple values of the same type in contiguous memory locations. Instead of declaring separate variables for each value, arrays let you group related data under a single name.
Think of an array like a row of mailboxes in an apartment building. Each mailbox (element) has a number (index) that identifies it, and they are all lined up next to each other (contiguous). Just like you can put mail in any mailbox by knowing its number, you can store or retrieve data from any array element by knowing its index. This simple concept is one of the most powerful tools in programming!
Why Do We Need Arrays?
Imagine you need to store the test scores of 100 students. Without arrays, you would need to
declare 100 separate variables: score1, score2, score3, and so on.
This approach is tedious, error-prone, and makes it nearly impossible to process the data efficiently.
Arrays solve this problem elegantly. With a single declaration, you can create a container that holds all 100 scores, and you can access any score using its position number (called an index). This makes looping through data, searching, sorting, and other operations straightforward.
Array
An array is a collection of elements of the same data type stored in contiguous (adjacent) memory locations. Each element can be accessed directly using an integer index.
In C, arrays have a fixed size that must be known at compile time (for static arrays). Once declared, you cannot change the size of an array - this is different from dynamic data structures like linked lists.
Key characteristics: Fixed size, same data type for all elements, zero-based indexing, contiguous memory allocation, direct access via index in O(1) time.
The Problem: Without Arrays
Consider storing temperatures for a week. Without arrays, you would write:
// Without arrays - tedious and hard to manage!
int temp_monday = 72;
int temp_tuesday = 75;
int temp_wednesday = 79;
int temp_thursday = 81;
int temp_friday = 78;
int temp_saturday = 74;
int temp_sunday = 71;
// Calculating average requires listing every variable
int sum = temp_monday + temp_tuesday + temp_wednesday +
temp_thursday + temp_friday + temp_saturday + temp_sunday;
float avg = sum / 7.0;
printf("Average: %.1f\n", avg); // 75.7
We declare 7 separate variables for each day's temperature. To calculate the average, we manually add all 7 variables together, divide by 7.0 (using a decimal to get a float result), and print the result.
The Solution: With Arrays
Now see how elegant the same task becomes with an array:
// With arrays - clean and scalable!
int temps[7] = {72, 75, 79, 81, 78, 74, 71};
// Calculate average using a loop
int sum = 0;
for (int i = 0; i < 7; i++) {
sum += temps[i];
}
float avg = sum / 7.0;
printf("Average: %.1f\n", avg); // 75.7
int temps[7] creates an array of 7 integers and initializes
them with the values in curly braces. The for loop runs from i=0 to i=6 (while i < 7).
Each iteration, temps[i] accesses the element at index i and adds it to sum.
How Arrays Are Stored in Memory
Understanding how arrays are stored in memory is crucial for C programmers. When you declare an array, the compiler allocates a contiguous block of memory - meaning all elements are stored one after another with no gaps.
This contiguous layout is what makes array access so fast. To find any element, the computer
calculates: address = base_address + (index * element_size). This simple arithmetic
means accessing arr[0] is just as fast as accessing arr[1000] -
both are O(1) constant time operations.
| Index | 0 | 1 | 2 | 3 | 4 |
|---|---|---|---|---|---|
| Value | 10 | 20 | 30 | 40 | 50 |
| Address | 1000 | 1004 | 1008 | 1012 | 1016 |
Memory layout of int arr[5] = {10, 20, 30, 40, 50}; (assuming 4-byte integers)
#include <stdio.h>
int main() {
int arr[5] = {10, 20, 30, 40, 50};
// Print addresses of each element
for (int i = 0; i < 5; i++) {
printf("arr[%d] = %d, address = %p\n", i, arr[i], (void*)&arr[i]);
}
// Notice addresses differ by 4 (size of int)
printf("\nSize of int: %zu bytes\n", sizeof(int)); // 4
printf("Total array size: %zu bytes\n", sizeof(arr)); // 20
return 0;
}
The loop prints each element's value and memory address using %p
format specifier. The &arr[i] gets the address of element i. Notice the addresses
increase by 4 each time (because each int is 4 bytes). sizeof(arr) returns
the total bytes used by the entire array (20 = 5 elements x 4 bytes).
number_of_elements * sizeof(element_type). For 5 integers at 4 bytes each,
that's 5 * 4 = 20 bytes.
Key Characteristics of C Arrays
Understanding these four fundamental properties will help you use arrays correctly and avoid common pitfalls. Each characteristic has important implications for how you write your code:
Fixed Size at Compile Time
When you declare an array like int arr[10], the compiler allocates exactly 40 bytes (10 × 4 bytes per int) at compile time. This size is permanent - you cannot add an 11th element later.
Implication: Plan your array size carefully. If you need dynamic sizing, you'll need to learn about malloc() and dynamic memory allocation later in the course.
Homogeneous Data Only
Every element in a C array must be the same data type. An int array holds only integers, a float array holds only floats. You cannot mix int, char, and float in one array.
Implication: This allows the compiler to calculate memory addresses efficiently. If you need mixed types, you'll use struct (covered in Module 6).
O(1) Direct Access
Arrays provide constant-time access to any element using its index. Whether you access arr[0] or arr[9999], it takes the same amount of time because the address is calculated directly: base_address + (index × element_size).
Implication: This makes arrays extremely fast for reading/writing - no need to traverse through elements like linked lists require.
No Automatic Bounds Checking
C completely trusts the programmer. If you access arr[100] on a 10-element array, C won't stop you - it will read/write whatever memory is at that location, causing undefined behavior (crashes, data corruption, security vulnerabilities).
Warning: Always validate indices manually! Use if (index >= 0 && index < size) before accessing. Buffer overflows are a major source of security bugs.
Declaration and Initialization
Before using an array in C, you must declare it with a specific data type and size. Understanding the different ways to declare and initialize arrays is essential for writing efficient C programs.
Declaration means telling the compiler: "I need space for X number of items of type Y." This is like reserving seats at a restaurant - you specify how many seats you need before you sit down. Initialization means filling those seats with actual values. You can declare first and initialize later, or do both at the same time.
Basic Array Declaration Syntax
The general syntax for declaring an array in C follows this pattern:
data_type array_name[array_size];
// Examples:
int numbers[10]; // Array of 10 integers
float prices[5]; // Array of 5 floats
char name[50]; // Array of 50 characters (string)
double values[100]; // Array of 100 doubles
Each declaration has three parts: (1) data_type - what kind
of values the array holds, (2) array_name - the identifier you use to reference it,
(3) [size] - how many elements it can store. The size in brackets must be a positive integer.
const variable). Variable-length arrays (VLAs) are
optional in C11 and not always supported.
Initialization Methods
C provides several ways to initialize arrays. The method you choose depends on whether you know the values at compile time and whether you want to specify the size explicitly.
Method 1: Initialize with All Values
// Initialize with all values explicitly
int scores[5] = {90, 85, 78, 92, 88};
// You can omit the size - compiler counts elements
int grades[] = {95, 87, 91, 88}; // Size is 4
// Float array with decimal values
float temps[] = {72.5, 75.3, 79.8, 81.2, 78.6};
Method 2: Partial Initialization
When you provide fewer values than the array size, remaining elements are automatically initialized to zero. This is a useful feature for creating zero-filled arrays.
// Partial initialization - remaining elements become 0
int arr[5] = {10, 20}; // arr = {10, 20, 0, 0, 0}
// Initialize entire array to zeros
int zeros[100] = {0}; // All 100 elements are 0
// Same with an empty initializer
int zeros2[50] = {}; // All 50 elements are 0 (C23 feature)
Method 3: Designated Initializers (C99)
C99 introduced designated initializers that let you initialize specific elements by index. Unspecified elements are set to zero.
// Designated initializers - set specific indices
int arr[10] = {[0] = 5, [5] = 10, [9] = 15};
// Result: {5, 0, 0, 0, 0, 10, 0, 0, 0, 15}
// Mix with regular initialization
int mix[6] = {1, 2, [4] = 8, 9};
// Result: {1, 2, 0, 0, 8, 9}
// Useful for sparse arrays or named indices
#define MONDAY 0
#define FRIDAY 4
int workdays[5] = {[MONDAY] = 8, [FRIDAY] = 6}; // Hours worked
Complete Declaration Examples
#include <stdio.h>
int main() {
// Method 1: Full initialization
int full[5] = {1, 2, 3, 4, 5};
// Method 2: Size inferred from initializer
int inferred[] = {10, 20, 30}; // Size = 3
// Method 3: Partial initialization (rest = 0)
int partial[5] = {100, 200}; // {100, 200, 0, 0, 0}
// Method 4: All zeros
int zeros[4] = {0}; // {0, 0, 0, 0}
// Method 5: Designated initializers
int designated[5] = {[2] = 50, [4] = 99};
// Print sizes
printf("full size: %zu\n", sizeof(full) / sizeof(full[0])); // 5
printf("inferred size: %zu\n", sizeof(inferred) / sizeof(inferred[0])); // 3
return 0;
}
This example shows 5 different initialization methods in action.
The sizeof(arr) / sizeof(arr[0]) trick divides the total array size by the size of
one element to get the count. For designated[5], only indices 2 and 4 have values;
the rest default to 0.
Calculating Array Size
A common C idiom is calculating array size at runtime using sizeof. This is
especially useful when you let the compiler infer the size from the initializer.
int numbers[] = {10, 20, 30, 40, 50, 60, 70};
// Calculate number of elements
size_t count = sizeof(numbers) / sizeof(numbers[0]);
printf("Array has %zu elements\n", count); // 7
// Use in a loop - automatically adapts if you add/remove elements
for (size_t i = 0; i < sizeof(numbers) / sizeof(numbers[0]); i++) {
printf("%d ", numbers[i]);
}
sizeof(numbers) returns the total bytes (7 ints x 4 bytes = 28).
sizeof(numbers[0]) returns the size of one element (4 bytes). Dividing gives us 7 elements.
The size_t type is an unsigned integer type designed for sizes and counts.
#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))
Then use: for (i = 0; i < ARRAY_SIZE(numbers); i++)
Common Declaration Mistakes
// Cannot assign array to array
int a[3] = {1, 2, 3};
int b[3];
b = a; // ERROR!
// Cannot redeclare
int arr[5];
int arr[5] = {1,2,3,4,5}; // ERROR!
// Size must be positive constant
int n = 5;
int dynamic[n]; // May not compile
// Copy element by element
int a[3] = {1, 2, 3};
int b[3];
for (int i = 0; i < 3; i++)
b[i] = a[i];
// Or use memcpy
#include <string.h>
memcpy(b, a, sizeof(a));
// Use constant for size
#define SIZE 5
int arr[SIZE] = {1,2,3,4,5};
Practice Questions: Declaration
Test your understanding of array declaration and initialization.
Task: Declare an array of 10 integers and initialize all elements to 0.
View Solution
int arr[10] = {0};
Or: int arr[10] = {}; (C23 feature)
Given:
int x[6] = {5, [3] = 10, 20};
Task: What are the values in this array after initialization?
View Solution
{5, 0, 0, 10, 20, 0}
First element is 5, indices 1-2 are 0 (unspecified), index 3 is 10 (designated), index 4 gets 20 (continues from designated position), index 5 is 0.
Task: Write code to declare an array, let the compiler determine its size, then print how many elements it contains using sizeof.
View Solution
int nums[] = {10, 20, 30, 40, 50};
size_t count = sizeof(nums) / sizeof(nums[0]);
printf("Elements: %zu\n", count); // 5
Accessing and Modifying Elements
Each element in an array is accessed using its index - a number that represents its position. Like most programming languages, C uses zero-based indexing, meaning the first element is at index 0.
The bracket notation arrayName[index] is your key to working with arrays.
You use it to both read values ("What is in position 3?") and write values ("Put 42 in position 3").
This simple syntax makes arrays incredibly easy to use once you remember that counting starts at zero!
Understanding Zero-Based Indexing
In C, array indices start at 0, not 1. For an array of size N, valid indices are 0 through N-1. This is called zero-based indexing and is used because the index represents the offset from the array's starting address.
| Position | First | Second | Third | Fourth | Fifth |
|---|---|---|---|---|---|
| Index | 0 | 1 | 2 | 3 | 4 |
| Access | arr[0] |
arr[1] |
arr[2] |
arr[3] |
arr[4] |
For an array of size 5, valid indices are 0 through 4
Reading and Writing Array Elements
The bracket notation arr[index] is used both for reading values from an
array and for writing values to an array. The expression arr[i] represents
the element at position i.
#include <stdio.h>
int main() {
int scores[5] = {85, 90, 78, 92, 88};
// Reading elements
printf("First score: %d\n", scores[0]); // 85
printf("Third score: %d\n", scores[2]); // 78
printf("Last score: %d\n", scores[4]); // 88
// Modifying elements
scores[2] = 95; // Change third element from 78 to 95
printf("Updated third score: %d\n", scores[2]); // 95
return 0;
}
scores[0] reads the first element (index 0 = 85).
scores[4] reads the last element (index 4 = 88, remember we start at 0!).
scores[2] = 95 writes the value 95 into index 2, replacing the old value 78.
The same bracket syntax works for both reading and writing.
Iterating Over Arrays with Loops
The real power of arrays comes from combining them with loops. A single loop can process thousands of elements, making your code concise and maintainable.
Using a for Loop (Most Common)
#include <stdio.h>
int main() {
int numbers[] = {10, 20, 30, 40, 50};
int size = sizeof(numbers) / sizeof(numbers[0]);
// Print all elements
printf("Array elements: ");
for (int i = 0; i < size; i++) {
printf("%d ", numbers[i]);
}
printf("\n"); // Output: 10 20 30 40 50
// Calculate sum
int sum = 0;
for (int i = 0; i < size; i++) {
sum += numbers[i];
}
printf("Sum: %d\n", sum); // 150
return 0;
}
The loop variable i starts at 0 and goes up to
size - 1 (which is 4). Each iteration, numbers[i] accesses the next element.
When i=0 we get 10, when i=1 we get 20, and so on. The sum += numbers[i] is shorthand
for sum = sum + numbers[i].
Using a while Loop
int arr[] = {5, 10, 15, 20, 25};
int size = 5;
int i = 0;
// Print in reverse order
printf("Reverse: ");
while (i < size) {
printf("%d ", arr[size - 1 - i]);
i++;
}
// Output: 25 20 15 10 5
Common Array Operations
Finding Maximum
int max = arr[0];
for (int i = 1; i < size; i++) {
if (arr[i] > max) {
max = arr[i];
}
}
printf("Max: %d\n", max);
Start by assuming the first element is the maximum. Loop through remaining elements; if any is larger, update max. After the loop, max holds the largest value.
Finding Minimum
int min = arr[0];
for (int i = 1; i < size; i++) {
if (arr[i] < min) {
min = arr[i];
}
}
printf("Min: %d\n", min);
Same logic as finding maximum, but check if each element is smaller than current min. The loop skips index 0 since we already used it as the initial value.
Linear Search
int target = 30, found = -1;
for (int i = 0; i < size; i++) {
if (arr[i] == target) {
found = i;
break;
}
}
// found = index or -1 if not found
Initialize found to -1 (not found). Loop through each element; if it matches target, store the index and break to exit early. If no match, found stays -1.
Reversing Array
for (int i = 0; i < size/2; i++) {
int temp = arr[i];
arr[i] = arr[size-1-i];
arr[size-1-i] = temp;
}
// Array is now reversed
Swap elements from both ends, moving toward the center. Only loop to size/2 since each iteration swaps two elements. Use a temp variable to hold one value during the swap.
Array Bounds and Safety
C does not perform automatic bounds checking. If you access an index outside the valid range (0 to size-1), you get undefined behavior. The program might crash, corrupt data, or appear to work but produce wrong results.
int arr[5] = {1, 2, 3, 4, 5};
// OUT OF BOUNDS - Undefined behavior!
printf("%d\n", arr[5]); // No 6th element!
printf("%d\n", arr[-1]); // Negative index!
arr[10] = 99; // Writing out of bounds!
// Could crash, corrupt memory, or seem to work
int arr[5] = {1, 2, 3, 4, 5};
int size = 5;
// Always check bounds
for (int i = 0; i < size; i++) { // i < 5, not i <= 5
printf("%d ", arr[i]);
}
// Validate user input
if (index >= 0 && index < size) {
printf("%d\n", arr[index]);
}
<= instead of < in loops.
For an array of size 5, for (i = 0; i <= 5; i++) accesses index 5, which is
out of bounds! Always use < size.
Practice Questions: Accessing Elements
Test your understanding of array indexing and element access.
Given:
int arr[4] = {10, 20, 30, 40};
Task: What is the value of arr[2]?
View Solution
30
Remember, indexing starts at 0: arr[0]=10, arr[1]=20, arr[2]=30, arr[3]=40.
Task: Write a loop to print all elements of an array in reverse order.
View Solution
for (int i = size - 1; i >= 0; i--) {
printf("%d ", arr[i]);
}
Start from the last valid index (size-1) and decrement until reaching 0.
Given:
int arr[3] = {1, 2, 3};
for (int i = 0; i <= 3; i++) arr[i] *= 2;
Task: What is wrong with this code?
View Solution
The loop condition i <= 3 allows i to reach 3, but valid indices are only 0, 1, 2.
When i=3, arr[3] is out of bounds - this causes undefined behavior!
Fix: Use i < 3 instead of i <= 3.
Multi-dimensional Arrays
Multi-dimensional arrays are arrays of arrays. The most common type is the 2D array, which you can visualize as a table with rows and columns - perfect for representing matrices, game boards, or tabular data.
Imagine a spreadsheet like Microsoft Excel or Google Sheets. Each cell is identified by a row number and a column letter (like B3). A 2D array works the same way, except both dimensions use numbers. You need two indices to locate any element: one for the row, one for the column.
- Chess or checkers board (8x8 grid)
- Seating chart (rows and seats)
- Pixel screen (width x height)
- Spreadsheet data (rows x columns)
arr[2][3] means "row 2, column 3"
(remembering both start at 0).
Declaring 2D Arrays
A 2D array is declared by specifying two dimensions: rows and columns. The first dimension is the number of rows, and the second is the number of columns.
// Syntax: data_type name[rows][columns];
int matrix[3][4]; // 3 rows, 4 columns (12 elements total)
float grades[5][3]; // 5 students, 3 subjects each
char board[8][8]; // 8x8 chess board
| Col 0 | Col 1 | Col 2 | Col 3 | |
|---|---|---|---|---|
| Row 0 | [0][0] | [0][1] | [0][2] | [0][3] |
| Row 1 | [1][0] | [1][1] | [1][2] | [1][3] |
| Row 2 | [2][0] | [2][1] | [2][2] | [2][3] |
Layout of a 3x4 2D array: int matrix[3][4];
Initializing 2D Arrays
// Method 1: Nested braces (recommended for clarity)
int matrix[3][3] = {
{1, 2, 3}, // Row 0
{4, 5, 6}, // Row 1
{7, 8, 9} // Row 2
};
// Method 2: Single list (fills row by row)
int grid[2][4] = {1, 2, 3, 4, 5, 6, 7, 8};
// grid[0] = {1, 2, 3, 4}
// grid[1] = {5, 6, 7, 8}
// Method 3: Partial initialization (rest = 0)
int sparse[3][3] = {{1}, {0, 2}, {0, 0, 3}};
// Result: {{1,0,0}, {0,2,0}, {0,0,3}} - diagonal matrix!
Method 1 uses nested braces - each inner {} is one row.
Method 2 fills values left-to-right, row-by-row (first 4 values go to row 0, next 4 to row 1).
Method 3 shows partial init - {{1}} means row 0 gets 1 in first position, rest are 0.
Accessing 2D Array Elements
Use two indices to access elements: the first for row, the second for column. Both indices are zero-based.
#include <stdio.h>
int main() {
int matrix[3][3] = {
{1, 2, 3},
{4, 5, 6},
{7, 8, 9}
};
// Access single elements
printf("Center: %d\n", matrix[1][1]); // 5
printf("Top-right: %d\n", matrix[0][2]); // 3
printf("Bottom-left: %d\n", matrix[2][0]); // 7
// Modify an element
matrix[1][1] = 50;
printf("New center: %d\n", matrix[1][1]); // 50
return 0;
}
matrix[1][1] accesses row 1, column 1 - the center element (5).
matrix[0][2] is row 0 (first row), column 2 (third column) = 3.
matrix[2][0] is row 2 (last row), column 0 (first column) = 7.
You can modify any element using the same bracket notation with assignment.
Iterating with Nested Loops
Processing 2D arrays typically requires nested loops - an outer loop for rows and an inner loop for columns.
#include <stdio.h>
int main() {
int matrix[3][4] = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}
};
int rows = 3, cols = 4;
// Print the matrix
printf("Matrix contents:\n");
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
printf("%3d ", matrix[i][j]);
}
printf("\n");
}
// Calculate sum of all elements
int sum = 0;
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
sum += matrix[i][j];
}
}
printf("Sum: %d\n", sum); // 78
return 0;
}
The outer loop (i) iterates through rows (0, 1, 2).
For each row, the inner loop (j) iterates through columns (0, 1, 2, 3).
So we visit: [0][0], [0][1], [0][2], [0][3], then [1][0], [1][1]... and so on.
The %3d format prints each number in a 3-character wide field for neat alignment.
Memory Layout of 2D Arrays
In C, 2D arrays are stored in row-major order - all elements of row 0 are stored first, then all elements of row 1, and so on. This is important for understanding performance and pointer arithmetic.
int arr[2][3], memory layout is:
arr[0][0], arr[0][1], arr[0][2], arr[1][0], arr[1][1], arr[1][2] -
all contiguous in memory.
Practice Questions: 2D Arrays
Test your understanding of multi-dimensional arrays.
Task: Declare a 2D array representing a 4x4 identity matrix (1s on diagonal, 0s elsewhere).
View Solution
int identity[4][4] = {
{1, 0, 0, 0},
{0, 1, 0, 0},
{0, 0, 1, 0},
{0, 0, 0, 1}
};
The identity matrix has 1s along the main diagonal where row index equals column index.
Task: Write code to find the sum of each row in a 3x3 matrix.
View Solution
for (int i = 0; i < 3; i++) {
int rowSum = 0;
for (int j = 0; j < 3; j++) {
rowSum += matrix[i][j];
}
printf("Row %d sum: %d\n", i, rowSum);
}
The outer loop iterates rows, inner loop sums all columns in each row.
Arrays and Functions
Passing arrays to functions is a common operation in C programming. Unlike regular variables, arrays are passed by reference (actually by pointer), which means functions can modify the original array data.
When you pass a regular variable (like int x = 5;) to a function, the function gets
a copy of that value. Changing it inside the function does not affect the original. But
arrays work differently! When you pass an array, the function gets direct access to the original
data - any changes made inside the function will persist after the function returns.
printArray(arr, size).
Passing Arrays to Functions
When you pass an array to a function, you are actually passing a pointer to the first element. This has important implications: the function does not receive a copy - it works directly on the original array.
#include <stdio.h>
// Three equivalent ways to declare array parameters:
void printArray1(int arr[], int size); // Array notation (common)
void printArray2(int *arr, int size); // Pointer notation (explicit)
void printArray3(int arr[10], int size); // Size hint (ignored by compiler)
// Implementation
void printArray(int arr[], int size) {
printf("Array: ");
for (int i = 0; i < size; i++) {
printf("%d ", arr[i]);
}
printf("\n");
}
int main() {
int numbers[] = {10, 20, 30, 40, 50};
int size = sizeof(numbers) / sizeof(numbers[0]);
printArray(numbers, size); // Pass array and its size
// Output: Array: 10 20 30 40 50
return 0;
}
The function declaration void printArray(int arr[], int size)
shows that arr[] receives the array (actually a pointer to it) and size tells
the function how many elements to process. In main(), we call printArray(numbers, size)
- notice we pass just the array name without brackets.
sizeof(arr) returns the size of a pointer (8 bytes on
64-bit systems), not the array size.
Functions Can Modify Original Arrays
Since arrays are passed by reference, any changes made inside the function affect the original array in the caller.
#include <stdio.h>
// Double every element in the array
void doubleElements(int arr[], int size) {
for (int i = 0; i < size; i++) {
arr[i] *= 2; // Modifies the original array!
}
}
int main() {
int nums[] = {1, 2, 3, 4, 5};
int size = 5;
printf("Before: ");
for (int i = 0; i < size; i++) printf("%d ", nums[i]);
printf("\n"); // Output: 1 2 3 4 5
doubleElements(nums, size);
printf("After: ");
for (int i = 0; i < size; i++) printf("%d ", nums[i]);
printf("\n"); // Output: 2 4 6 8 10
return 0;
}
The doubleElements function modifies arr[i] *= 2
(which means arr[i] = arr[i] * 2). After calling the function, the original nums
array in main() is permanently changed from {1,2,3,4,5} to {2,4,6,8,10}. This is because
arrays are passed by reference, not copied!
Returning Arrays from Functions
C functions cannot directly return arrays by value. However, you can return a pointer to a static or dynamically allocated array, or modify an array passed as a parameter.
// WRONG - returns pointer to local
int* createArray() {
int arr[5] = {1, 2, 3, 4, 5};
return arr; // DANGER! arr is gone
}
// Accessing returned pointer = crash
// Option 1: Fill caller's array
void fillArray(int arr[], int size) {
for (int i = 0; i < size; i++)
arr[i] = i + 1;
}
// Option 2: Static array (caution!)
int* getStatic() {
static int arr[5] = {1,2,3,4,5};
return arr; // OK, static lasts
}
Passing 2D Arrays to Functions
For 2D arrays, you must specify the column size in the parameter. The row size can be omitted, but the column size is required for the compiler to calculate offsets.
#include <stdio.h>
#define COLS 3
// Column size must be specified
void printMatrix(int matrix[][COLS], int rows) {
for (int i = 0; i < rows; i++) {
for (int j = 0; j < COLS; j++) {
printf("%3d ", matrix[i][j]);
}
printf("\n");
}
}
int main() {
int mat[2][COLS] = {
{1, 2, 3},
{4, 5, 6}
};
printMatrix(mat, 2);
return 0;
}
The function parameter int matrix[][COLS] leaves row count empty but specifies column count (3).
This is required because C needs to know column width to calculate memory offsets.
The #define COLS 3 ensures the constant is shared between function and main.
We pass rows separately since the function can't determine it from the pointer.
Useful Array Functions to Implement
Find Maximum Element
int findMax(int arr[], int size) {
int max = arr[0];
for (int i = 1; i < size; i++) {
if (arr[i] > max) max = arr[i];
}
return max;
}
Initialize max with the first element. Loop starting from index 1 (we already have index 0).
If any element is greater than current max, update it. After the loop completes, return the largest value found.
Calculate Average
double average(int arr[], int size) {
int sum = 0;
for (int i = 0; i < size; i++) {
sum += arr[i];
}
return (double)sum / size;
}
Accumulate all elements into sum. The (double) cast before division is crucial -
without it, integer division would truncate the decimal part (150/7 = 21 instead of 21.43).
Returns a double for precision.
Reverse Array In-Place
void reverse(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;
}
}
Swap elements from both ends, moving toward the center. Only loop to size/2 since each iteration
swaps two elements (front and back). The temp variable is needed to hold one value during the swap.
"In-place" means we modify the original array without creating a copy.
Practice Questions: Arrays & Functions
Test your understanding of passing arrays to functions.
Task: Why must you pass the array size as a separate parameter to functions?
View Solution
Inside a function, an array parameter is actually a pointer. Using sizeof(arr) gives the size of the pointer (4 or 8 bytes), not the total array size.
The function has no way to know how many elements exist - you must pass this information explicitly.
Task: Write a function that counts how many even numbers are in an array.
View Solution
int countEven(int arr[], int size) {
int count = 0;
for (int i = 0; i < size; i++) {
if (arr[i] % 2 == 0) count++;
}
return count;
}
The modulo operator % 2 gives 0 for even numbers.
Task: What is wrong with returning a local array from a function?
View Solution
Local arrays are stored on the stack and are destroyed when the function returns.
The returned pointer becomes a dangling pointer, pointing to memory that is no longer valid. Accessing it causes undefined behavior - crashes, garbage data, or security vulnerabilities.
Solution: Use static arrays, pass array as parameter to fill, or dynamically allocate with malloc().
Interactive Demo
Experiment with arrays in real-time. Use the interactive visualizer below to understand how array indexing and memory layout work.
Array Index Visualizer
Enter values separated by commas to create an array, then click on elements to see their index and memory offset.
2D Array Index Calculator
Calculate memory addresses for 2D array elements using the row-major formula.
Formula: offset = (row * cols + col) * sizeof(int)
Linear Index: -
Byte Offset: - bytes
Access: arr[1][2]
Key Takeaways
Contiguous Memory
Array elements are stored in consecutive memory locations, enabling efficient access and iteration
Zero-Based Indexing
Array indices start at 0, so an array of size n has valid indices from 0 to n-1
Fixed Size
Array size must be known at compile time (for static arrays) and cannot be changed after declaration
No Bounds Checking
C does not check array bounds - accessing invalid indices causes undefined behavior
Multi-dimensional Arrays
2D arrays store data in row-major order and are accessed using two indices [row][col]
Passed by Reference
Arrays decay to pointers when passed to functions, allowing functions to modify original data
Knowledge Check
Quick Quiz
Test what you've learned about arrays in C