3. Pointers, Pointer Arithmetic & Array Decay
Memory model, pointer scaling, array decay rules (arr vs &arr), 2D subscript equivalence, complex declarations via Right-Left rule, and string literal pitfalls
GATE CS & UGC NET JRF Core Subject Exam Weightage: 3–4 Questions every year on pointer arithmetic scaling,
arrvs&arrtype differences, 2D array dereferencing (*(*(p + i) + j)), and decoding complex type declarations.
1. Technical Jargon & The Pointer Memory Model#
A pointer is not a mysterious entity; it is a variable whose stored value is a memory address pointing into the process's virtual address space.
Variable: x (int) Pointer: p (int*)
Address: 0x1000 Address: 0x2000
Value: 42 Value: 0x1000 (Stores address of x)
┌────────┐ ┌────────────┐
0x1000 │ 42 │ <————————————————─ │ 0x1000 │ 0x2000
└────────┘ └────────────┘
- Dereferencing (
*): Accessing the data stored at the memory location pointed to by the address. - Address-Of (
&): Retrieving the physical RAM address of an l-value object. - Pointer Scaling: In pointer arithmetic, adding an integer does not add bytes; it adds bytes.
ptrdiff_t: The signed integer type returned when subtracting two pointers of the same type.- Array Decay: The automatic implicit conversion of an array name into a pointer to its first element in most expression contexts.
- Generic Pointer (
void*): A pointer with no associated data type. Can hold the address of any object, but cannot be directly dereferenced or used in standard pointer arithmetic without a type cast. - Dangling Pointer: A pointer holding the address of memory that has already been deallocated or gone out of scope.
- Wild Pointer: An uninitialized pointer holding arbitrary garbage memory addresses.
2. Pointer Arithmetic & Scaling#
2.1 The Pointer Arithmetic Rule#
Given pointer T *p pointing to base address Addr:
int *pi = (int *)1000; // Assume sizeof(int) = 4
double *pd = (double *)1000; // Assume sizeof(double) = 8
pi + 2 ==> 1000 + (2 * 4) = 1008
pd + 2 ==> 1000 + (2 * 8) = 1016
2.2 Pointer Subtraction (p2 - p1)#
Subtracting two pointers that point to elements of the same array:
The result is the number of elements between them, not bytes!
Pointer Subtraction Precondition: Subtracting two pointers that do NOT point into the same array (or one past the end) is UNDEFINED BEHAVIOR in ISO C.
3. Array Decay Rules: arr vs. &arr#
In C, an array name decays to a pointer to its first element in almost all expressions:
int a[5] = {10, 20, 30, 40, 50};
The 3 Exceptions Where Array Does NOT Decay:#
sizeof(a): Returns total size of the array in bytes (), NOT pointer size!&a: Yields a pointer to the entire array of typeint (*)[5], NOTint*.- String literal initializing an array:
char s[] = "hello";copies the characters into stack storage.
The Big GATE Trap: What is the difference between a, &a[0], and &a?#
| Expression | Numeric Address Value | C Data Type | What does + 1 advance by? |
|---|---|---|---|
a | 0x1000 | int* (pointer to int) | 4 bytes (sizeof(int)) |
&a[0] | 0x1000 | int* (pointer to int) | 4 bytes (sizeof(int)) |
&a | 0x1000 | int (*)[5] (pointer to array of 5 ints) | 20 bytes (5 * sizeof(int)) |
#include <stdio.h>
int main(void) {
int a[5] = {1, 2, 3, 4, 5};
printf("a = %p, a + 1 = %p\n", (void*)a, (void*)(a + 1));
// a + 1 advances by 4 bytes (next integer)
printf("&a = %p, &a + 1 = %p\n", (void*)&a, (void*)(&a + 1));
// &a + 1 advances by 20 bytes (skips the ENTIRE array of 5 integers!)
// Classic GATE Exam Trick:
int *ptr = (int *)(&a + 1);
printf("%d\n", *(ptr - 1)); // Points to last element! Prints 5!
return 0;
}
4. Multi-Dimensional Arrays & Subscript Equivalence#
4.1 Subscript Equivalence Principle#
In C, array subscript notation is syntactic sugar for pointer arithmetic:
For 2D Arrays:
Why is i[a] valid C?
Because addition is commutative: a[i] is defined as *(a + i), which is identical to *(i + a), which by definition equals i[a]!
3[arr] is completely legal and accesses arr[3].
4.2 Pointer to Array vs. Array of Pointers#
// 1. Pointer to an Array of 5 Integers:
int (*p)[5]; // 'p' is ONE pointer. sizeof(p) is 8 bytes (on 64-bit).
// p + 1 skips 5 * 4 = 20 bytes.
// 2. Array of 5 Pointers to Integer:
int *p[5]; // 'p' is an ARRAY of 5 pointers.
// sizeof(p) is 5 * 8 = 40 bytes (on 64-bit).
5. Decoding Complex Declarations: The Right-Left / Spiral Rule#
In GATE and UGC NET, you will encounter declarations like int *(*(*foo)())[10]. You decode any complex C declaration using the Right-Left (Clockwise Spiral) Rule:
1. Start at the identifier name.
2. Look RIGHT: If you see '()', it's a function; if you see '[]', it's an array.
3. Look LEFT: If you see '*', it's a pointer to...
4. When parentheses are encountered, resolve everything inside them before moving outside.
Example: void (*bsd_signal(int sig, void (*func)(int)))(int);
1. Start at identifier: bsd_signal
2. Look Right: (int sig, void (*func)(int)) --> is a function taking an int and a function pointer...
3. Look Left: * --> returning a pointer to...
4. Move Outside Right: (int) --> a function taking an int...
5. Move Outside Left: void --> returning void.
Common Complex Declarations Reference Table:#
| Declaration | Human Readable Meaning |
|---|---|
int *p[10] | Array of 10 pointers to int |
int (*p)[10] | Pointer to an array of 10 ints |
int (*p)(void) | Pointer to a function taking void and returning int |
int *(*p)(void) | Pointer to a function taking void and returning pointer to int |
int (*p[10])(int) | Array of 10 pointers to functions taking int and returning int |
int (*(*p)[10])(void) | Pointer to an array of 10 function pointers returning int |
6. String Literals: Read-Only Segment vs. Stack Arrays#
// Case 1: Pointer to String Literal
char *str1 = "Hello";
str1[0] = 'M'; // UNDEFINED BEHAVIOR! Segfault on modern OS (stored in read-only .rodata)
// Case 2: Character Array initialized with String Literal
char str2[] = "Hello";
str2[0] = 'M'; // FULLY LEGAL! Array is allocated on the Stack and mutable ("Mello")
7. Best Practices & Defensive Pointer Habits#
- Initialize All Pointers Immediately:
If a pointer has no target yet, assign
NULL:int *p = NULL;. - Zero Out Freed Pointers:
After calling
free(p), immediately setp = NULL;to prevent accidental dangling pointer dereferencing. - Use
sizeof(*ptr)in Allocation: Writep = malloc(n * sizeof(*p));instead ofmalloc(n * sizeof(int));. If the type ofpchanges, the allocation remains automatically correct! - Const Correctness for Read-Only Buffers:
If a function only reads a buffer, mark parameter as
const int *buf.
Practice Quiz
Test your understanding with step-by-step solutions
Practice Quiz
10 questions · 90s per question
Each question has a 90-second time limit. Unanswered questions will be auto-submitted when time runs out.