1D & 2D Arrays with Pointers — 5 Examples
0%
Arrays & Pointers  ·  5 Examples

1D & 2D Arrays
with Pointers in C

Five focused programs covering 1D array traversal via pointers, pointer arithmetic, passing arrays to functions, 2D arrays with row pointers, and a real-world marks-processing mini-app — all with memory diagrams and step-by-step traces.

1
1D Array & Pointer Walk
2
Pointer Arithmetic
3
Array to Function
4
2D Array & Row Ptrs
5
Marks Mini-App

🧠 Arrays and Pointers — The Core Relationship

In C, an array name is a constant pointer to its first element. When you write int arr[5], the name arr holds the address of arr[0]. This means arr == &arr[0] is always true. You can assign the array's address to a pointer: int *p = arr — now p and arr point to the same memory.

Because of this, every array access using [] is secretly pointer arithmetic. The compiler converts arr[i] into *(arr + i) internally. Both forms produce identical machine code. You can also increment a pointer variable to walk through an array: p++ advances by sizeof(int) bytes, landing on the next element.

For 2D arrays, int mat[3][4] is a contiguous block of 12 integers in memory. The row mat[r] is itself a pointer to the first element of row r, and mat[r][c] is equivalent to *(*(mat + r) + c). Understanding this flattened layout is the key to passing 2D arrays to functions correctly.

array — pointer equivalence table
arr[i]
*(arr + i)
← subscript is syntactic sugar for pointer dereference
&arr[i]
arr + i
← address of element i = base + i × sizeof(T)
arr == &arr[0]
always true
← array name decays to pointer to first element
mat[r][c]
*(*(mat + r) + c)
← double dereference for 2D arrays
example 1
1
📌 1D Array — Four Ways to Access with Pointers
Index notation, pointer+index, pointer dereference, and pointer increment — all produce the same result
1D + Pointer
A 1D array is a contiguous block of same-type elements. We store temperature readings for 7 days and access every element using four equivalent methods: the familiar arr[i] index notation; pointer-plus-offset *(arr + i); a pointer variable with index p[i]; and a walking pointer *p++. All four produce identical machine code — the compiler treats them the same. The program also prints each element's address to confirm that consecutive elements are exactly sizeof(int) bytes apart.
ex1_1d_pointer.c
C
#include <stdio.h>

int main() {
    int temp[] = { 32, 35, 29, 41, 38, 27, 33 };
    int  n    = sizeof(temp) / sizeof(temp[0]);
    int *p    = temp;   /* p points to temp[0] */

    printf("=== Temperatures (7 days) ===\n\n");

    /* Method 1: arr[i] — classic index notation */
    printf("Method 1 — arr[i]          : ");
    for (int i = 0; i < n; i++) printf("%d ", temp[i]);
    printf("\n");

    /* Method 2: *(arr + i) — pointer arithmetic */
    printf("Method 2 — *(arr+i)        : ");
    for (int i = 0; i < n; i++) printf("%d ", *(temp + i));
    printf("\n");

    /* Method 3: p[i] — pointer used like an array */
    printf("Method 3 — p[i]            : ");
    for (int i = 0; i < n; i++) printf("%d ", p[i]);
    printf("\n");

    /* Method 4: *p++ — walk pointer forward */
    printf("Method 4 — *p++            : ");
    p = temp;   /* reset to start */
    for (int i = 0; i < n; i++) printf("%d ", *p++);
    printf("\n");

    /* Show addresses — each element is 4 bytes apart */
    printf("\n--- Addresses (each 4 bytes apart) ---\n");
    for (int i = 0; i < n; i++)
        printf("  temp[%d] = %2d  address = %p  (arr+%d = %p)\n",
               i, temp[i], (void*)&temp[i],
               i, (void*)(temp + i));

    printf("\narr == &arr[0] : %s\n",
           temp == &temp[0] ? "TRUE" : "FALSE");
    return 0;
}
output
=== Temperatures (7 days) ===

Method 1 — arr[i]          : 32 35 29 41 38 27 33
Method 2 — *(arr+i)        : 32 35 29 41 38 27 33
Method 3 — p[i]            : 32 35 29 41 38 27 33
Method 4 — *p++            : 32 35 29 41 38 27 33

--- Addresses (each 4 bytes apart) ---
  temp[0] = 32  address = 0x7ffd1000  (arr+0 = 0x7ffd1000)
  temp[1] = 35  address = 0x7ffd1004  (arr+1 = 0x7ffd1004)
  temp[2] = 29  address = 0x7ffd1008  (arr+2 = 0x7ffd1008)
  temp[3] = 41  address = 0x7ffd100c  (arr+3 = 0x7ffd100c)
  temp[4] = 38  address = 0x7ffd1010  (arr+4 = 0x7ffd1010)
  temp[5] = 27  address = 0x7ffd1014  (arr+5 = 0x7ffd1014)
  temp[6] = 33  address = 0x7ffd1018  (arr+6 = 0x7ffd1018)

arr == &arr[0] : TRUE
1D array in memory — contiguous, 4 bytes per int element
temp[0..6]
32
35
29
41
38
27
33
← 7 × 4 = 28 bytes
address offset
+0
+4
+8
+12
+16
+20
+24
← each step = sizeof(int) = 4
p after p++
1000
1004
1008
100c
1010
1014
1018
← pointer moves 4 bytes per ++
You cannot increment the array name itself. temp++ is a compile error because temp is a constant — its value is permanently fixed to the start of the array. Copy it to a pointer variable first (int *p = temp) and then you can increment p freely without losing the array's base address.
example 2
2
➕ Pointer Arithmetic — Reverse, Search, Sum
Use pointer subtraction, comparison, and increment to reverse an array, find an element, and sum without any index variable
Ptr Arithmetic
Pointer arithmetic lets you write elegant array algorithms without ever using an index variable. Adding an integer to a pointer advances it by that many elements. Subtracting two pointers of the same type gives the number of elements between them. Comparing two pointers with <, ==, > tells you their relative positions in memory. This example implements three classic algorithms — in-place reversal using a left/right pointer squeeze, linear search by pointer comparison, and a summation loop — all using only pointer operations.
ex2_ptr_arithmetic.c
C
#include <stdio.h>

void printArr(int *p, int n) {
    for (int i = 0; i < n; i++) printf("%d ", *p++);
    printf("\n");
}

/* Reverse array in-place using two pointers */
void reverseArr(int *arr, int n) {
    int *lo = arr;           /* left  pointer */
    int *hi = arr + n - 1;  /* right pointer */
    while (lo < hi) {
        int tmp = *lo;
        *lo++ = *hi;
        *hi-- = tmp;
    }
}

/* Linear search: return pointer to match, NULL if absent */
int* findVal(int *arr, int n, int target) {
    int *end = arr + n;
    for (int *p = arr; p < end; p++)
        if (*p == target) return p;
    return NULL;
}

/* Sum using only pointer walk, no index variable */
int sumArr(int *arr, int n) {
    int sum = 0;
    int *end = arr + n;
    for (int *p = arr; p < end; p++) sum += *p;
    return sum;
}

int main() {
    int scores[] = { 88, 42, 95, 67, 73, 55, 91 };
    int n = sizeof(scores) / sizeof(scores[0]);

    printf("Original : "); printArr(scores, n);

    /* Sum */
    printf("Sum      : %d\n", sumArr(scores, n));
    printf("Average  : %.2f\n\n", (double)sumArr(scores, n) / n);

    /* Search */
    int  target = 73;
    int *found  = findVal(scores, n, target);
    if (found)
        printf("Search %d : found at index %td  address %p\n\n",
               target, found - scores, (void*)found);
    else
        printf("Search %d : not found\n\n", target);

    /* Reverse */
    reverseArr(scores, n);
    printf("Reversed : "); printArr(scores, n);

    /* Pointer subtraction demo */
    int *first = scores;
    int *last  = scores + n - 1;
    printf("\nlast - first = %td elements apart\n", last - first);
    return 0;
}
output
Original : 88 42 95 67 73 55 91
Sum      : 511
Average  : 73.00

Search 73 : found at index 4  address 0x7ffd1010

Reversed : 91 55 73 67 95 42 88

last - first = 6 elements apart
Pointer subtraction gives element count, not byte count. last - first returns 6 (indices apart), not 24 (bytes apart). The compiler automatically divides the byte difference by sizeof(int). Use (char*)last - (char*)first if you need the raw byte difference — but in normal code, the element count is what you want.
for (int *p = arr; p < end; p++) is idiomatic C for iterating an array by pointer. It is equivalent to for (int i = 0; i < n; i++) and compiles to the same code. Many experienced C programmers prefer the pointer form for clarity when the index variable is not needed elsewhere in the loop body.
example 3
3
📬 Passing Arrays to Functions — by Pointer
Arrays always pass as pointers — the function receives the address and can modify the original; const protects read-only access
Array → Function
When you pass an array to a function in C, only the pointer to the first element is passed — never a copy of the whole array. Inside the function, int arr[] and int *arr are completely identical declarations. Because the function receives the original address, any modification it makes changes the actual array in the caller. Declaring the parameter as const int *arr signals read-only intent and lets the compiler catch accidental writes. This program shows all three scenarios: a read-only function, a modifying function, and a function that returns statistics through pointer output parameters.
ex3_array_to_fn.c
C
#include <stdio.h>

/* READ-ONLY: const protects the array */
void printArr(const int *arr, int n) {
    printf("  [ ");
    for (int i = 0; i < n; i++) printf("%d ", arr[i]);
    printf("]\n");
}

/* MODIFYING: doubles every element in-place */
void doubleAll(int *arr, int n) {
    for (int i = 0; i < n; i++) arr[i] *= 2;
}

/* STATISTICS: multiple return values via output pointers */
void stats(const int *arr, int n,
           int *outMin, int *outMax, double *outAvg) {
    int sum = 0;
    *outMin = *outMax = arr[0];
    for (int i = 0; i < n; i++) {
        sum += arr[i];
        if (arr[i] < *outMin) *outMin = arr[i];
        if (arr[i] > *outMax) *outMax = arr[i];
    }
    *outAvg = (double)sum / n;
}

/* CLAMP: replaces out-of-range values in-place */
int clamp(int *arr, int n, int lo, int hi) {
    int changes = 0;
    for (int *p = arr, *end = arr + n; p < end; p++) {
        if      (*p < lo) { *p = lo; changes++; }
        else if (*p > hi) { *p = hi; changes++; }
    }
    return changes;
}

int main() {
    int marks[] = { 45, 112, 78, -5, 92, 63, 150, 55 };
    int n = sizeof(marks) / sizeof(marks[0]);

    printf("Original marks:\n"); printArr(marks, n);

    /* Stats before clamp */
    int mn, mx; double avg;
    stats(marks, n, &mn, &mx, &avg);
    printf("  Min=%d  Max=%d  Avg=%.2f\n\n", mn, mx, avg);

    /* Clamp to valid range [0..100] */
    int c = clamp(marks, n, 0, 100);
    printf("After clamp [0..100] — %d value(s) fixed:\n", c);
    printArr(marks, n);

    /* Stats after clamp */
    stats(marks, n, &mn, &mx, &avg);
    printf("  Min=%d  Max=%d  Avg=%.2f\n\n", mn, mx, avg);

    /* Double all marks (modifying) */
    doubleAll(marks, n);
    printf("After doubleAll (modified in-place):\n");
    printArr(marks, n);
    return 0;
}
output
Original marks:
  [ 45 112 78 -5 92 63 150 55 ]
  Min=-5  Max=150  Avg=73.75

After clamp [0..100] — 3 value(s) fixed:
  [ 45 100 78 0 92 63 100 55 ]
  Min=0  Max=100  Avg=66.63

After doubleAll (modified in-place):
  [ 90 200 156 0 184 126 200 110 ]
Arrays always pass by pointer — there is no "pass by value" for arrays in C. Any function that receives an array can modify the caller's data unless the parameter is declared const. If you need an unmodified copy inside the function, you must manually copy the array with memcpy before modifying it. This is different from passing a single int, which always passes a copy.
example 4
4
🗺️ 2D Array — Row Pointers and Flat Memory
A 3×4 grid of seats stored in a 2D array — access via double subscript, row pointer, and single pointer to the flat block
2D Array
A 2D array like int mat[3][4] is stored as a flat, contiguous block of 12 integers in row-major order — all of row 0 first, then row 1, then row 2. The row name mat[r] is a pointer to the first element of row r. You can access any element three equivalent ways: mat[r][c], *(mat[r] + c), or through a flat pointer *(flat + r*COLS + c). This program uses a classroom seating grid to demonstrate all three access methods plus how to pass a 2D array to a function correctly.
ex4_2d_array.c
C
#include <stdio.h>

#define ROWS 3
#define COLS 4

/* Function accepting 2D array — column count must be known */
void printGrid(int grid[][COLS], int rows, const char *title) {
    printf("  %s\n", title);
    for (int r = 0; r < rows; r++) {
        printf("  Row %d: ", r);
        for (int c = 0; c < COLS; c++)
            printf("%4d", grid[r][c]);
        printf("\n");
    }
}

/* Fill grid with roll numbers: seat (r,c) = r*100 + c+1 */
void fillGrid(int grid[][COLS], int rows) {
    for (int r = 0; r < rows; r++)
        for (int c = 0; c < COLS; c++)
            grid[r][c] = (r + 1) * 100 + (c + 1);
}

int main() {
    int seats[ROWS][COLS];
    fillGrid(seats, ROWS);

    printf("=== Classroom Seating Grid (%dx%d) ===\n\n", ROWS, COLS);
    printGrid(seats, ROWS, "Roll numbers by seat:");

    /* Three equivalent access methods */
    int r = 1, c = 2;
    printf("\n--- Three ways to access seat [%d][%d] ---\n", r, c);
    printf("  seats[r][c]           = %d\n", seats[r][c]);
    printf("  *(seats[r] + c)       = %d\n", *(seats[r] + c));
    printf("  *(*(seats+r) + c)     = %d\n", *(*(seats+r) + c));

    /* Flat pointer — treat 2D array as 1D */
    printf("\n--- Flat pointer walk (row-major order) ---\n  ");
    int *flat = &seats[0][0];
    for (int i = 0; i < ROWS * COLS; i++) {
        printf("%4d", *(flat + i));
        if ((i + 1) % COLS == 0) printf("\n  ");
    }

    /* Row pointers */
    printf("\n--- Row pointer addresses ---\n");
    for (int row = 0; row < ROWS; row++)
        printf("  seats[%d] (row ptr) = %p   "
               "seats[%d][0] = %d\n",
               row, (void*)seats[row], row, seats[row][0]);

    /* Modify one cell via flat pointer */
    *(flat + 1 * COLS + 3) = 999;   /* seat [1][3] */
    printf("\nAfter *(flat+7)=999 (seat[1][3]):\n");
    printGrid(seats, ROWS, "");
    return 0;
}
output
=== Classroom Seating Grid (3x4) ===

  Roll numbers by seat:
  Row 0:  101 102 103 104
  Row 1:  201 202 203 204
  Row 2:  301 302 303 304

--- Three ways to access seat [1][2] ---
  seats[r][c]           = 203
  *(seats[r] + c)       = 203
  *(*(seats+r) + c)     = 203

--- Flat pointer walk (row-major order) ---
   101 102 103 104
   201 202 203 204
   301 302 303 304

--- Row pointer addresses ---
  seats[0] (row ptr) = 0x7ffd1000   seats[0][0] = 101
  seats[1] (row ptr) = 0x7ffd1010   seats[1][0] = 201
  seats[2] (row ptr) = 0x7ffd1020   seats[2][0] = 301

After *(flat+7)=999 (seat[1][3]):
  Row 0:  101 102 103 104
  Row 1:  201 202 203 999
  Row 2:  301 302 303 304
2D array flat memory layout — row-major, all 12 elements contiguous
Row 0
101
102
103
104
← offset 0..3
Row 1
201
202
203
999
← offset 4..7, [1][3]=999
Row 2
301
302
303
304
← offset 8..11
Formula
flat[r*COLS + c]
← e.g. [1][3] = flat[1×4+3] = flat[7]
When passing a 2D array to a function, the column size must be known at compile time. Declare the parameter as int grid[][COLS] or int (*grid)[COLS]. The compiler needs COLS to calculate row offsets. Only the first dimension can be omitted. Passing a int ** for a 2D array is wrong — it assumes a different memory layout.
example 5
5
🎓 Student Marks Mini-App — 1D + 2D Arrays Together
Store marks for 4 students across 3 subjects in a 2D array; use pointer-based functions to compute totals, rank, and print a report
Mini-App
This mini-app ties together everything from Examples 1–4 in one practical program. A marks[4][3] array stores marks for 4 students across 3 subjects. Pointer-based functions compute each student's total (sum along a row), the class average per subject (sum down a column), and find the topper by comparing row sums. A final report is printed using a function pointer for formatting. The entire program uses only pointer arithmetic — no index variables inside any helper function.
ex5_marks_app.c
C
#include <stdio.h>
#include <string.h>

#define STUDENTS 4
#define SUBJECTS 3

const char *names[] = { "Ananya", "Rohan", "Priya", "Karan" };
const char *subs[]  = { "Maths",  "Science", "English" };

/* Sum a row (one student's marks) using pointer walk */
int rowSum(int *row, int n) {
    int s = 0;
    for (int *p = row, *end = row + n; p < end; p++) s += *p;
    return s;
}

/* Average of a column (all students for one subject) */
double colAvg(int grid[][SUBJECTS], int rows, int col) {
    int s = 0;
    for (int r = 0; r < rows; r++) s += grid[r][col];
    return (double)s / rows;
}

/* Find index of student with highest total */
int topperIdx(int grid[][SUBJECTS], int rows) {
    int best = 0;
    for (int r = 1; r < rows; r++)
        if (rowSum(grid[r], SUBJECTS) >
            rowSum(grid[best], SUBJECTS)) best = r;
    return best;
}

/* Grade from total out of 300 */
char grade(int total) {
    if (total >= 270) return 'A';
    if (total >= 240) return 'B';
    if (total >= 210) return 'C';
    if (total >= 180) return 'D';
    return 'F';
}

int main() {
    int marks[STUDENTS][SUBJECTS] = {
        { 88, 92, 79 },   /* Ananya */
        { 74, 68, 85 },   /* Rohan  */
        { 95, 91, 97 },   /* Priya  */
        { 61, 77, 70 },   /* Karan  */
    };

    /* Header */
    printf("=== Student Marks Report ===\n\n");
    printf("%-10s %7s %9s %9s  %5s  %5s  Grade\n",
           "Name", subs[0], subs[1], subs[2], "Total", "Avg");
    printf("%s\n", "-------------------------------------------------------------");

    /* Per-student row using row pointer */
    for (int r = 0; r < STUDENTS; r++) {
        int *row   = marks[r];   /* pointer to start of row r */
        int  total = rowSum(row, SUBJECTS);
        printf("%-10s %7d %9d %9d  %5d  %5.1f  %c\n",
               names[r],
               row[0], row[1], row[2],
               total,
               (double)total / SUBJECTS,
               grade(total));
    }

    /* Subject averages using column pointer arithmetic */
    printf("%s\n", "-------------------------------------------------------------");
    printf("%-10s "  , "Class Avg");
    for (int c = 0; c < SUBJECTS; c++)
        printf("%9.1f", colAvg(marks, STUDENTS, c));
    printf("\n\n");

    /* Topper */
    int top = topperIdx(marks, STUDENTS);
    printf("Topper : %s  (Total=%d  Grade=%c)\n",
           names[top],
           rowSum(marks[top], SUBJECTS),
           grade(rowSum(marks[top], SUBJECTS)));

    /* Flat pointer: find first mark below 70 */
    printf("\nMarks below 70 (flat pointer scan):\n");
    int *flat = &marks[0][0];
    for (int i = 0; i < STUDENTS * SUBJECTS; i++) {
        if (flat[i] < 70)
            printf("  %s / %s = %d\n",
                   names[i / SUBJECTS],
                   subs[i % SUBJECTS],
                   flat[i]);
    }
    return 0;
}
output
=== Student Marks Report ===

Name        Maths   Science   English  Total    Avg  Grade
-------------------------------------------------------------
Ananya         88        92        79    259   86.3  B
Rohan          74        68        85    227   75.7  C
Priya          95        91        97    283   94.3  A
Karan          61        77        70    208   69.3  D
-------------------------------------------------------------
Class Avg       79.5      82.0      82.8

Topper : Priya  (Total=283  Grade=A)

Marks below 70 (flat pointer scan):
  Rohan / Science = 68
  Karan / Maths = 61
marks[4][3] flat layout — row pointer and flat pointer access compared
marks[0] (Ananya)
88
92
79
← flat[0..2], row ptr = &marks[0][0]
marks[1] (Rohan)
74
68
85
← flat[3..5], 68 is below 70
marks[2] (Priya)
95
91
97
← flat[6..8], topper
marks[3] (Karan)
61
77
70
← flat[9..11], 61 is below 70
Column access
grid[r][col] steps SUBJECTS
← colAvg jumps by SUBJECTS per row
The flat pointer idiom flat[i / COLS] and flat[i % COLS] converts a flat index back to row and column — invaluable when you need to scan a 2D array as a single sequence (e.g., searching all cells) without nested loops. Dividing by COLS gives the row; taking the remainder gives the column.
Row pointers are the cleanest way to work with individual rows. int *row = marks[r] gives you a normal 1D pointer you can pass to any function that takes int * — like rowSum(marks[r], SUBJECTS). This lets you reuse 1D array functions for individual rows of a 2D array without any extra boilerplate.
checklist
  • Ex 1 — Array name = constant pointer to arr[0]. arr[i] == *(arr+i) == p[i] == *p++. All four are identical. Cannot do arr++ — copy to int *p = arr first.
  • Ex 2 — Pointer subtraction gives element count (not bytes). for (int *p = arr; p < end; p++) is idiomatic. Reverse with two-pointer squeeze: lo < hi, swap *lo++ and *hi--.
  • Ex 3 — Arrays always pass as pointers — the function can modify the original. Use const int *arr for read-only access. Output parameters return multiple values: int *outMin, int *outMax.
  • Ex 4 — 2D array mat[R][C] is a flat R×C block in row-major order. Access: mat[r][c] == *(mat[r]+c) == *(*(mat+r)+c) == flat[r*C+c]. Pass as int grid[][COLS] — column size required.
  • Ex 5 — Row pointer int *row = marks[r] lets you reuse 1D functions on any row. Flat pointer &marks[0][0] scans all cells; recover row and col with i/COLS and i%COLS. colAvg steps by SUBJECTS per row.