Pointers and Arrays in C for GATE CS
Pointer arithmetic, pointer to array vs array of pointers, double pointers, dynamic memory, and function pointers — with GATE-level output tracing examples.
Last updated: April 2026 | GATE CS 2024–2026 syllabus
Key Takeaways for GATE
- Pointer arithmetic:
p + nadvances byn × sizeof(*p)bytes. a[i]is exactly equivalent to*(a + i)— arrays decay to pointers to their first element.- Declaration trap:
int *p[5]= array of 5 int-pointers.int (*p)[5]= pointer to 5-element int array. sizeof(array)gives total bytes;sizeof(pointer)gives address size (4 or 8 bytes) — different!- 2D array
a[R][C]: elementa[i][j]at offset(i*C + j) * sizeof(int)from base. mallocreturns uninitialized memory;calloczero-initializes. Both returnvoid*.- Function pointer:
int (*fp)(int, int)— call asfp(a, b)or(*fp)(a, b).
1. Pointer Basics & Declarations
int *p; — p is a pointer to int (stores address of an int).&x — address-of operator: gives address of variable x.*p — dereference operator: gives value at address p. Reading complex declarations (right-to-left rule):
int *p → p is a pointer to int
int *p[5] → p is an array[5] of pointer to int
int (*p)[5] → p is a pointer to array[5] of int
int *(*p)[5] → p is a pointer to array[5] of pointer to int
int (*f)(int) → f is a pointer to function taking int, returning int
int *f(int) → f is a function taking int, returning pointer to int
2. Pointer Arithmetic
p points to type T at address A:p + n has address A + n × sizeof(T)p - n has address A − n × sizeof(T)p1 - p2 = number of elements between two pointers (same array)Example (int array, 4 bytes per int):
int a[5] = {10, 20, 30, 40, 50};
int *p = a; // p → a[0], address 1000
p + 1 // address 1004, value 20
p + 3 // address 1012, value 40
*(p + 2) // value at 1008 = 30 = a[2] Pointer to char vs pointer to int:
char *cp; cp+1 → advances 1 byte. int *ip; ip+1 → advances 4 bytes.
3. Arrays & Pointers — The Critical Relationship
int a[5]; → a decays to &a[0] (type: int*) Equivalences:
a[i] ≡ *(a + i) ≡ i[a] (yes, 3[a] is valid C!)
Where array does NOT decay:
sizeof(a) = total bytes (20 for int[5]) — NOT sizeof(pointer)
&a = type is int(*)[5] — pointer to whole array, not first element
Key difference:
int a[5]; int *p = a; sizeof(a) → 20 // array knows its full size sizeof(p) → 4 // pointer is just an address
When array is passed to function, it decays — function receives pointer, loses size info.
4. 2D Arrays
int a[R][C] — stored in row-major order.Address of
a[i][j] = base + (i×C + j) × sizeof(int) Type of 2D array name:
a decays to int (*)[C] — pointer to array of C ints (NOT int**)
Passing 2D arrays to functions:
void f(int a[][5], int rows) { ... } // column count must be specified
void f(int (*a)[5], int rows) { ... } // equivalentGATE output trace:
int a[2][3] = {{1,2,3},{4,5,6}};
int *p = &a[0][0];
printf("%d %d", *(p+3), *(p+5)); // a[1][0]=4, a[1][2]=6 → "4 6"5. Double Pointers
int **pp — pointer to pointer to int.Use cases: (1) modify a pointer from a function, (2) dynamically allocated 2D arrays, (3) argv in main.
Modifying pointer through function:
void alloc(int **pp) {
*pp = malloc(sizeof(int) * 10); // sets caller's pointer
}
int *p = NULL;
alloc(&p); // now p points to allocated blockDynamic 2D array:
int **a = malloc(R * sizeof(int*)); for(int i=0;i<R;i++) a[i] = malloc(C * sizeof(int)); // access: a[i][j]. NOT contiguous in memory (unlike int a[R][C]).
6. Dynamic Memory Allocation
| Function | Signature | Initializes? | Notes |
|---|---|---|---|
| malloc | void* malloc(size_t n) | No (garbage) | Allocate n bytes |
| calloc | void* calloc(size_t n, size_t size) | Yes (zeros) | Allocate n×size bytes |
| realloc | void* realloc(void* p, size_t n) | No (for new part) | Resize allocation |
| free | void free(void* p) | — | Release memory |
Memory leaks: allocated memory not freed before program exit or pointer reassignment.
Dangling pointer: pointer used after free(). Set to NULL after free.
Double free: calling free() twice on same pointer — undefined behaviour.
7. Function Pointers
return_type (*name)(param_types)
int add(int a, int b) { return a+b; }
int (*fp)(int, int) = add; // or = &add (both valid)
int result = fp(3, 4); // or (*fp)(3, 4) — both call addTypedef for cleaner code:
typedef int (*BinaryOp)(int, int); BinaryOp fp = add;
Use cases: callbacks, dispatch tables, implementing polymorphism in C, qsort comparator.
qsort comparator: int cmp(const void *a, const void *b)
8. const with Pointers
const int *p — pointer to const int: can change p, cannot change *p.int * const p — const pointer to int: cannot change p, can change *p.const int * const p — const pointer to const int: cannot change either.Memory aid: Read right-to-left. If const is left of *, value is const. If right of *, pointer is const.
9. GATE Examples
int a[] = {1, 2, 3, 4, 5};
int *p = a;
printf("%d %d %d", *p, *(p+2), *(p+4));p points to a[0]=1. p+2 → a[2]=3. p+4 → a[4]=5. Output: 1 3 5
int a[3][4];
int (*p)[4] = a;
printf("%lu", sizeof(p)); // 8 on 64-bit, 4 on 32-bit — pointer size
printf("%lu", sizeof(*p)); // 4*4 = 16 — size of one row (int[4])sizeof(p) = 4 (or 8). sizeof(*p) = 16.
int x = 10, *p = &x, **pp = &p;
**pp = 20;
printf("%d", x); // x changed via double pointerOutput: 20
10. Common Mistakes
sizeof(array)vssizeof(pointer): After passing array to function, you get pointer —sizeofgives 4 or 8, not the full array size. Cannot recover array size inside function without passing it explicitly.int *p[5]vsint (*p)[5]: [] has higher precedence than *.int *p[5]is array of pointers;int (*p)[5]is pointer to array. GATE tests this every year.- Returning pointer to local variable: Local variables are on the stack and destroyed on function return. Returning their address = dangling pointer.
- Dynamic 2D array is not contiguous:
int **awith malloc gives non-contiguous rows — cannot pass to functions expectingint a[][C]. - Forgetting to free: Every malloc must have a matching free. In GATE programs, trace memory carefully for use-after-free and double-free.
11. FAQ
- What is the difference between a pointer to an array and an array of pointers?
int (*p)[5]is a pointer to an array of 5 ints — p holds the address of the entire 5-element array, and sizeof(*p) = 20.int *p[5]is an array of 5 pointers to int — p is a 5-element array where each element holds an address. [] binds tighter than * in declarations, so the parentheses are critical.- What does pointer arithmetic do?
- Adding n to a pointer of type T* advances it by n × sizeof(T) bytes in memory. This is why
a[i]equals*(a+i)— the compiler automatically scales the index by the element size. Subtracting two pointers gives the number of elements between them. - What is the difference between malloc and calloc?
- malloc(n) allocates n bytes with uninitialized (garbage) content. calloc(n, sz) allocates n×sz bytes and sets every byte to zero. calloc is slower due to zeroing but safer when you need guaranteed zero initialization. Both return void* on success and NULL on failure.
- When is a pointer a null pointer vs a dangling pointer?
- A null pointer is explicitly set to 0/NULL and points to nothing — dereferencing it is UB but predictably crashes (SIGSEGV). A dangling pointer still contains the old address of freed or out-of-scope memory — it may appear to work but read/write corrupted data. Always set pointers to NULL after free() to convert dangling pointers to null pointers.