🧠 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.
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.
#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; }
=== 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
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.<, ==, > 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.
#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; }
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
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.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.
#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; }
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 ]
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.
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.
#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; }
=== 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
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.
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.
#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; }
=== 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
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.
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.- Ex 1 — Array name = constant pointer to arr[0].
arr[i]==*(arr+i)==p[i]==*p++. All four are identical. Cannot doarr++— copy toint *p = arrfirst. - 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 *arrfor 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 asint 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 withi/COLSandi%COLS. colAvg steps by SUBJECTS per row.