E1
Array name is already a pointer โ no & needed
arr and &arr[0] are the same address โ you can use arr like a pointer directly
Array = Pointer
Most people write
int *p = &arr[0] to get a pointer to the first element. But the array name itself is already that pointer. Writing int *p = arr is identical. This means arr[2] and *(arr+2) and *(p+2) and p[2] all produce the exact same value โ four different syntaxes for one operation.
arr in memory โ arr == &arr[0] == p
#include <stdio.h> int main() { int arr[] = {10, 20, 30, 40, 50}; int *p = arr; /* same as &arr[0] โ no & needed */ /* All four lines print the SAME value: 30 */ printf("arr[2] = %d\n", arr[2]); /* normal index */ printf("*(arr+2) = %d\n", *(arr+2)); /* pointer math */ printf("*(p+2) = %d\n", *(p+2)); /* via pointer */ printf("p[2] = %d\n", p[2]); /* pointer as array */ /* Proof: same address */ printf("\narr addr = %p\n", (void*)arr); printf("&arr[0] addr = %p\n", (void*)&arr[0]); printf("p addr = %p\n", (void*)p); return 0; }
arr[2] = 30 *(arr+2) = 30 *(p+2) = 30 p[2] = 30 arr addr = 0x7ffd1a2b3c40 &arr[0] addr = 0x7ffd1a2b3c40 p addr = 0x7ffd1a2b3c40
Uncommon fact: you can use subscript
[ ] on any pointer, not just arrays. p[2] is literally defined as *(p+2) in C. Even this works: 2[p] โ because 2[p] = *(2+p) = *(p+2). Addition is commutative.e2 โ pointer doing subscript
E2
Pointer subscript โ walking a string with p[i] instead of p++
A char* can be subscripted like an array โ and the index can even go negative
Pointer [ ] Access
A
char* pointer can use [ ] just like an array โ because they are the same operation. The uncommon trick: if you advance the pointer first (p++ or p += k), then p[-1] gives the character before the current position. Negative subscripts are legal in C as long as the address remains within the original array's bounds.
#include <stdio.h> int main() { char word[] = "Ananta"; char *p = word; /* p โ 'A' */ p += 3; /* advance: p โ 'n' (index 3) */ printf("p[0] = %c\n", p[0]); /* 'n' โ current position */ printf("p[1] = %c\n", p[1]); /* 't' โ one ahead */ printf("p[-1] = %c\n", p[-1]); /* 'a' โ one BEHIND (legal!) */ printf("p[-3] = %c\n", p[-3]); /* 'A' โ back to start */ /* Reverse print using negative subscript */ int len = 6; p = word + len - 1; /* p โ last char 'a' */ printf("Reversed: "); for(int i = 0; i < len; i++) printf("%c", p[-i]); /* p[0] p[-1] p[-2] ... */ printf("\n"); return 0; }
p[0] = n p[1] = t p[-1] = a p[-3] = A Reversed: atnanA
Negative subscript is legal as long as the computed address stays inside the original array.
p[-1] just means *(p-1) โ one step before wherever p currently points. This is used in audio DSP buffers, circular queues, and boundary checks.e3 โ pointer to the middle
E3
Pointer to the middle โ split one array into two views
Two pointers, same array โ left half and right half โ no copying needed
Mid-Array Pointer
You can point into the middle of an existing array and treat it as a separate array โ no copy, no malloc.
int *right = arr + 5 means right[0] is arr[5], right[1] is arr[6], and so on. This is how C handles substrings, sub-arrays, and efficient partition algorithms โ by handing a pointer to the relevant position instead of copying data.
left points to arr[0], right points to arr[5] โ same memory, two views
#include <stdio.h> /* Print n elements via pointer โ reusable */ void printN(const int *p, int n, const char *label) { printf("%-8s: [ ", label); for(int i = 0; i < n; i++) printf("%d ", p[i]); printf("]\n"); } int main() { int arr[] = {10,20,30,40,50,60,70,80,90,100}; int *left = arr; /* view of first half */ int *right = arr + 5; /* view of second half */ printN(left, 5, "left"); /* 10 20 30 40 50 */ printN(right, 5, "right"); /* 60 70 80 90 100 */ /* right[0] IS arr[5] โ same memory */ right[0] = 999; printf("arr[5] after right[0]=999: %d\n", arr[5]); /* 999 */ /* Distance between two pointers */ printf("right - left = %td elements\n", right - left); /* 5 */ return 0; }
left : [ 10 20 30 40 50 ] right : [ 60 70 80 90 100 ] arr[5] after right[0]=999: 999 right - left = 5 elements
No copy was made.
left and right both point into the same array. Writing through right[0] changes arr[5]. This is how qsort, binary search, and merge sort work โ they pass sub-array pointers, not copies.e4 โ pointer to pointer
E4
Pointer to a pointer โ int **pp โ two levels of indirection
pp stores the address of p, which stores the address of x โ reach x through two arrows
Double Pointer
int **pp is a pointer to a pointer to int. It stores the address of another pointer. To get to the actual integer you dereference twice: **pp. The uncommon use: you can change which variable p points to from inside a function, by passing &p as int **pp. Single pointer can change the value โ double pointer can change the pointer itself.
three variables, two levels of indirection
#include <stdio.h> /* Changes which variable ptr POINTS AT โ needs int** */ void redirectPtr(int **pp, int *newTarget) { *pp = newTarget; /* change what p points to */ } int main() { int x = 42; int y = 99; int *p = &x; /* p โ x */ int**pp = &p; /* pp โ p */ printf("*p = %d\n", *p); /* 42 โ through p */ printf("**pp = %d\n", **pp); /* 42 โ through pp then p */ /* Change value through double pointer */ **pp = 100; printf("x after **pp=100: %d\n", x); /* 100 */ /* Re-point p at y โ using double pointer */ redirectPtr(&p, &y); printf("*p after redirect: %d\n", *p); /* 99 */ return 0; }
*p = 42 **pp = 42 x after **pp=100: 100 *p after redirect: 99
Single pointer modifies the value. Double pointer modifies the pointer. If a function receives
int *p and does p = &y, the caller's pointer is unchanged. But with int **pp and *pp = &y, the caller's pointer is redirected. This is why scanf takes &variable โ it needs a pointer to write through.e5 โ array of 5 pointers passed to a function
E5
Array of 5 pointers โ passed to a function โ printed inside
int *arr[5] holds 5 addresses โ function receives int **arr โ dereferences each
int *arr[5] โ Function
int *arr[5] is an array where each of the 5 slots holds a pointer to a different integer. When you pass this array to a function, the function receives it as int **arr โ a pointer to the first element, where each element is itself a pointer. Inside the function, arr[i] is the i-th pointer, and *arr[i] is the integer it points at. Two levels: array slot โ pointer โ integer.
int *arr[5] โ 5 slots, each holding a pointer to a different int
Declaration side
int *arr[5]
โ array of 5 slots
โ each slot is int*
arr[0] = &a (address of a)
arr[1] = &b etc.
โ array of 5 slots
โ each slot is int*
arr[0] = &a (address of a)
arr[1] = &b etc.
Function side
void fn(int **arr, int n)
โ receives int** (ptr to first ptr)
arr[i] โ the i-th pointer
*arr[i] โ the actual integer
โ receives int** (ptr to first ptr)
arr[i] โ the i-th pointer
*arr[i] โ the actual integer
#include <stdio.h> /* Function receives int** โ array of pointers passed in */ void printAll(int **arr, int n) { printf(" Values via *arr[i]:\n"); for (int i = 0; i < n; i++) { printf(" arr[%d] points to address %p โ value = %d\n", i, (void*)arr[i], *arr[i]); } } /* Function doubles every value through the pointers */ void doubleAll(int **arr, int n) { for (int i = 0; i < n; i++) *arr[i] *= 2; /* dereference and modify */ } /* Function finds maximum by comparing through pointers */ int findMax(int **arr, int n) { int max = *arr[0]; for (int i = 1; i < n; i++) if (*arr[i] > max) max = *arr[i]; return max; } int main() { /* Five separate int variables */ int a = 10, b = 20, c = 30, d = 40, e = 50; /* Array of 5 pointers โ each holds an address */ int *arr[5]; arr[0] = &a; arr[1] = &b; arr[2] = &c; arr[3] = &d; arr[4] = &e; /* Pass array of pointers โ decays to int** */ printf("=== printAll ===\n"); printAll(arr, 5); printf("\n=== doubleAll ===\n"); doubleAll(arr, 5); /* modifies a,b,c,d,e through pointers */ printAll(arr, 5); printf("\n=== findMax ===\n"); printf(" Max = %d\n", findMax(arr, 5)); /* Proof: a,b,c,d,e themselves changed */ printf("\n=== original vars after doubleAll ===\n"); printf(" a=%d b=%d c=%d d=%d e=%d\n", a, b, c, d, e); return 0; }
=== printAll === arr[0] points to address 0x7ffd... โ value = 10 arr[1] points to address 0x7ffd... โ value = 20 arr[2] points to address 0x7ffd... โ value = 30 arr[3] points to address 0x7ffd... โ value = 40 arr[4] points to address 0x7ffd... โ value = 50 === doubleAll === arr[0] points to address 0x7ffd... โ value = 20 arr[1] points to address 0x7ffd... โ value = 40 arr[2] points to address 0x7ffd... โ value = 60 arr[3] points to address 0x7ffd... โ value = 80 arr[4] points to address 0x7ffd... โ value = 100 === findMax === Max = 100 === original vars after doubleAll === a=20 b=40 c=60 d=80 e=100
Why does the function receive
int **arr? Because int *arr[5] is an array of pointers. When an array is passed to a function, it decays to a pointer to its first element. The first element is an int*. So a pointer to int* is int**. Two-step: int *arr[5] โ first element type is int* โ pointer to that is int**.
The variables a, b, c, d, e actually changed after
doubleAll(). The function received pointers to them and wrote through those pointers with *arr[i] *= 2. This is how C functions modify multiple separate variables โ not by returning them, but by receiving their addresses and writing directly.checklist
- E1 โ Array IS a pointer:
int *p = arris same asint *p = &arr[0]. All four โarr[i],*(arr+i),p[i],*(p+i)โ give the same result.p[2]is defined as*(p+2). - E2 โ Negative subscript: After
p += 3, writingp[-1]is legal and means*(p-1)โ one step back. Legal as long as address stays inside the original array. - E3 โ Pointer to middle:
int *right = arr + 5creates a second view into the same array.right[0]ISarr[5]โ same memory, no copy. Pointer subtractionright - leftgives the distance in elements. - E4 โ Double pointer:
int **pppoints to a pointer.*ppgives the pointer,**ppgives the value. Pass&pasint**to let a function change which variableppoints at. - E5 โ Array of 5 pointers:
int *arr[5]โ 5 slots, each anint*. Pass to function asint **arr(array decays to pointer-to-first-element, first element isint*, so pointer-to-that isint**).arr[i]= i-th pointer.*arr[i]= i-th value. Writing*arr[i] = xchanges the original variable.