Arrays
Fixed-size sequences of elements — declaration, indexing, memory layout, and the connection to pointers.
What is an array?
Think of an array as a row of numbered mailboxes in an apartment building. All the boxes are the same size (they only hold one type of item), they are side by side in memory, and you reach any box instantly by its number. Box 0 is first. There is no box -1, and there is no box equal to or beyond the total count — accessing those is like breaking into the neighbor's wall. C will not stop you, but you will corrupt memory.
An array in C is a fixed-size, contiguous block of memory holding elements of the same type. "Fixed-size" means the size is set at compile time and cannot change. "Contiguous" means all elements are stored one after another in memory — element 0 immediately followed by element 1, then element 2, and so on.
The first index is always 0, and the last valid index is n-1 where n is the number of elements. C does not check whether your index is in bounds — accessing arr[5] in a 5-element array silently reads whatever bytes come after the array in memory. This is called undefined behavior and is a common source of security bugs.
Arrays and pointers are deeply connected. The name of an array (e.g., arr) decays to a pointer to its first element in most contexts. That means arr[i] is exactly the same as *(arr + i). You will understand this more deeply in Topic 08 (Pointers).
sizeof behaves differently for arrays vs pointers. sizeof(arr) on a stack-allocated array gives the total byte size. But once you pass an array to a function, it becomes a pointer and sizeof gives the pointer size (8 bytes on 64-bit), not the array size. You must always pass the array length separately.
# Python — dynamic list, any size, any type
nums = [1, 2, 3, 4, 5]
nums.append(6) # grows dynamically
length = len(nums) # built-in length
# Java — fixed size, but object with .length
int[] arr = new int[5];
int len = arr.length; // automatic field
// arr[10] throws IndexOutOfBoundsException
/* C — fixed size, one type, no auto-length */
int arr[5] = {1, 2, 3, 4, 5};
/* No .append — size is FIXED */
int len = sizeof(arr)/sizeof(arr[0]); /* 5 */
/* arr[10] is UNDEFINED BEHAVIOR — no crash! */
/* No VLAs in this course */
/* Always pass size separately to functions */
void process(int *arr, int n);
Variable-Length Arrays (VLAs) — where the size is a variable, e.g. int arr[n]; — are not permitted in this course. Their behavior is non-standard in C99 and was made optional in C11. Always use compile-time constants or dynamic allocation (malloc) for variable-sized collections.
sizeof(arr) / sizeof(arr[0]) gives the number of elements — but only works in the same scope where the array is declared. Inside a function that receives the array as a parameter, sizeof gives 8 (the pointer size), not the array size. Always pass the length as a separate int parameter.
Declaration, initialization, and memory layout
/* 1. Declaration — fixed size, one type */ int arr[5]; /* ^ ^ | └── size: 5 elements (compile-time constant) └──────── element type: int (4 bytes each → 20 bytes total) */ /* 2. Declaration with initializer list */ int arr[5] = {1, 2, 3, 4, 5}; /* 3. Size inferred from initializer */ int arr[] = {10, 20, 30}; /* size = 3, inferred */ /* 4. Partial init — remaining elements are 0 */ int arr[5] = {1}; /* {1, 0, 0, 0, 0} */ /* 5. All zeros */ int arr[5] = {0}; /* {0, 0, 0, 0, 0} */
sizeof(int) = 4 bytes. arr[0] is at address &arr[0], arr[1] is at &arr[0] + 4, etc. The elements are contiguous — no gaps between them.
Indexing and array arithmetic
int arr[5] = {10, 20, 30, 40, 50}; /* Standard index notation */ arr[0] /* → 10 (first element) */ arr[4] /* → 50 (last element, index n-1) */ /* Pointer arithmetic — identical result */ *(arr + 0) /* → 10 (same as arr[0]) */ *(arr + 2) /* → 30 (same as arr[2]) */ /* sizeof usage */ sizeof(arr) /* → 20 bytes (5 * 4) */ sizeof(arr) / sizeof(arr[0]) /* → 5 (number of elements) */ /* Array decays to pointer to first element */ int *p = arr; /* same as: int *p = &arr[0]; */
2D arrays
/* 2D array: int grid[rows][cols] */ int grid[3][4]; /* 3 rows, 4 columns → 12 ints → 48 bytes */ int g[2][3] = { {1, 2, 3}, /* row 0 */ {4, 5, 6} /* row 1 */ }; g[1][2] /* → 6 (row 1, column 2) */ /* Stored in row-major order: 1,2,3,4,5,6 in memory */
Arrays as function parameters
/* All three declarations below are IDENTICAL */ void foo(int *arr, int n); void foo(int arr[], int n); void foo(int arr[10], int n); /* size 10 is IGNORED */ /* Inside the function: */ void foo(int *arr, int n) { sizeof(arr); /* 8 — pointer size, NOT array size */ arr[0]; /* OK — arr is a pointer, indexing works */ }
Complete programs you can compile and run
#include <stdio.h>
int main(void) {
int arr[6] = {3, 7, 1, 9, 4, 6};
int n = sizeof(arr) / sizeof(arr[0]); /* 6 */
/* Sum all elements */
int sum = 0;
for (int i = 0; i < n; i++) {
sum += arr[i];
}
printf("Sum = %d\n", sum); /* 30 */
/* Find maximum */
int max = arr[0];
for (int i = 1; i < n; i++) {
if (arr[i] > max) max = arr[i];
}
printf("Max = %d\n", max); /* 9 */
/* Pointer arithmetic — same as arr[i] */
printf("arr[2] = %d, *(arr+2) = %d\n", arr[2], *(arr+2)); /* both 1 */
return 0;
}
Max = 9
arr[2] = 1, *(arr+2) = 1
#include <stdio.h>
/* Function receives pointer + length — no sizeof trick here! */
int sum_array(int *arr, int n) {
int total = 0;
for (int i = 0; i < n; i++) {
total += arr[i]; /* arr[i] == *(arr + i) */
}
return total;
}
void print_array(int arr[], int n) {
printf("[");
for (int i = 0; i < n; i++) {
printf("%d%s", arr[i], i < n-1 ? ", " : "");
}
printf("]\n");
}
int main(void) {
int data[] = {5, 10, 15, 20, 25};
int n = sizeof(data) / sizeof(data[0]); /* 5 — only works HERE */
print_array(data, n);
printf("Sum = %d\n", sum_array(data, n));
/* 2D array */
int matrix[2][3] = {{1,2,3},{4,5,6}};
for (int r = 0; r < 2; r++) {
for (int c = 0; c < 3; c++) {
printf("%d ", matrix[r][c]);
}
printf("\n");
}
return 0;
}
Sum = 75
1 2 3
4 5 6
#include <stdio.h>
int main(void) {
int arr[] = {10, 20, 30, 40, 50};
int n = 5;
/* Method 1: index-based */
for (int i = 0; i < n; i++) {
printf("%d ", arr[i]);
}
printf("\n");
/* Method 2: pointer-based (C idiom) */
int *end = arr + n; /* one past the last element */
for (int *p = arr; p < end; p++) {
printf("%d ", *p); /* dereference pointer */
}
printf("\n");
/* Both methods produce identical output */
return 0;
}
10 20 30 40 50
Practice problems with solutions
Write a function int count(int *arr, int n, int target) that returns how many times target appears in the array. Then call it from main to count how many times 3 appears in {3, 1, 3, 7, 3, 2}.
#include <stdio.h>
int count(int *arr, int n, int target) {
int cnt = 0;
for (int i = 0; i < n; i++) {
if (arr[i] == target) cnt++;
}
return cnt;
}
int main(void) {
int data[] = {3, 1, 3, 7, 3, 2};
int n = sizeof(data) / sizeof(data[0]);
printf("3 appears %d times\n", count(data, n, 3)); /* 3 */
return 0;
}
n. We iterate from 0 to n-1 and increment a counter each time the element equals the target. sizeof(data)/sizeof(data[0]) gives 6, computed in main where the array is still in scope.
The code below has an out-of-bounds access. Identify the bug and fix it.
int arr[5] = {1, 2, 3, 4, 5};
for (int i = 0; i <= 5; i++) { /* loop condition */
printf("%d\n", arr[i]);
}
int arr[5] = {1, 2, 3, 4, 5};
for (int i = 0; i < 5; i++) { /* FIX: < not <= */
printf("%d\n", arr[i]);
}
i <= 5 allows i to reach 5. Valid indices are 0–4. arr[5] reads memory just past the end of the array — undefined behavior. On some systems this silently prints garbage; on others it crashes. The fix is i < 5. As a rule: loop condition is always i < n, never i <= n.
What does the following print? Explain why.
#include <stdio.h>
void inspect(int arr[]) {
printf("Inside function: %zu\n", sizeof(arr));
}
int main(void) {
int data[10];
printf("In main: %zu\n", sizeof(data));
inspect(data);
return 0;
}
In main: 40
Inside function: 8
sizeof(int) = 4, 10 elements → 40 bytes. Why 8 in function: When passed to a function, the array decays to a pointer (int *). On 64-bit systems, a pointer is 8 bytes. The size information is gone. This is the "array decay" problem — always pass length separately. The parameter int arr[] is completely equivalent to int *arr.
Write a function void reverse(int *arr, int n) that reverses the array in-place (without allocating a new array). Test it on {1,2,3,4,5}.
#include <stdio.h>
void reverse(int *arr, int n) {
int left = 0, right = n - 1;
while (left < right) {
int tmp = arr[left];
arr[left] = arr[right];
arr[right] = tmp;
left++;
right--;
}
}
int main(void) {
int a[] = {1, 2, 3, 4, 5};
reverse(a, 5);
for (int i = 0; i < 5; i++) printf("%d ", a[i]); /* 5 4 3 2 1 */
printf("\n");
return 0;
}
reverse affect the original array in main.
Key concepts to memorize
Test your understanding
int arr[8];?LO1arr[i] and *(arr + i) always produce the same result.LO1int arr[10]; declared in main, what does sizeof(arr) return on a typical 64-bit system?LO8arr, write ______ / sizeof(arr[0]).LO1void foo(int arr[]) {
int n = sizeof(arr) / sizeof(arr[0]);
for (int i = 0; i < n; i++) printf("%d\n", arr[i]);
}
arr[5] on an array of size 5 in C?LO1