What is an array?

Real-World Analogy

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 / Java (what you know)
# 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 (what you are learning)
/* 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);
No VLAs in COMP2017

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.

The sizeof / sizeof trick for array length

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} */
Memory layout: Each element occupies 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 */
}
Key rule: An array passed to a function decays to a pointer. The size information is lost. Always pass the length explicitly as a second parameter.

Complete programs you can compile and run

Example 1 — Basic array operations: sum, max, iterate Week 1 Lecture
#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;
}
Output
Sum = 30
Max = 9
arr[2] = 1, *(arr+2) = 1
Example 2 — Passing array to function (decay) Week 2 Tutorial B
#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;
}
Output
[5, 10, 15, 20, 25]
Sum = 75
1 2 3
4 5 6
Example 3 — Iterating with a pointer (C idiom) Week 2 Lecture
#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;
}
Output
10 20 30 40 50
10 20 30 40 50

Practice problems with solutions

P1 — Write a function to count occurrences of a value Tutorial Wk2B

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;
}
Key points: The function receives a pointer (array decays) and a separate length 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.
P2 — Spot the out-of-bounds bug Tutorial Wk2B — Common Pitfalls

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]);
}
The bug: 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.
P3 — sizeof pitfall: array passed to function Tutorial Wk2B — sizeof Deep Dive

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
Why 40 in main: 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.
P4 — Reverse an array in-place Exam-style problem

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;
}
Algorithm: Two-pointer approach — swap the outermost pair, move pointers inward, repeat until they meet in the middle. Works for both even and odd lengths. Since arrays are passed by pointer, modifications inside reverse affect the original array in main.

Key concepts to memorize

Card 1 of 10
Question — click to flip
Answer
Click card to flip • Use buttons to navigate

Test your understanding

Topic 07 Quiz — Arrays Score: 0 / 6
1
What are the valid indices for an array declared as int arr[8];?LO1
multiple choice
2
True or False: arr[i] and *(arr + i) always produce the same result.LO1
true / false
3
Given int arr[10]; declared in main, what does sizeof(arr) return on a typical 64-bit system?LO8
multiple choice
4
Fill in the blank: to compute the number of elements in a stack-allocated array arr, write ______ / sizeof(arr[0]).LO1
fill in the blank
5
Spot the bug: what is wrong with this function?LO8
void foo(int arr[]) {
    int n = sizeof(arr) / sizeof(arr[0]);
    for (int i = 0; i < n; i++) printf("%d\n", arr[i]);
}
spot the bug — multiple choice
6
What happens when you access arr[5] on an array of size 5 in C?LO1
multiple choice
0/6
Quiz complete!