Pointers and Arrays in C for GATE CS – Complete Guide | EngineeringHulk


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 + n advances by n × 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]: element a[i][j] at offset (i*C + j) * sizeof(int) from base.
  • malloc returns uninitialized memory; calloc zero-initializes. Both return void*.
  • Function pointer: int (*fp)(int, int) — call as fp(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

If 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

Array decay: In most expressions, array name decays to pointer to first element.
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) { ... }  // equivalent

GATE 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 block

Dynamic 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

FunctionSignatureInitializes?Notes
mallocvoid* malloc(size_t n)No (garbage)Allocate n bytes
callocvoid* calloc(size_t n, size_t size)Yes (zeros)Allocate n×size bytes
reallocvoid* realloc(void* p, size_t n)No (for new part)Resize allocation
freevoid free(void* p)Release memory
Always check return value: malloc returns NULL on failure.
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

Declaration: 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 add

Typedef 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

GATE 2017 — What is the output?

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

GATE 2019 — Pointer to 2D array:

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.

GATE 2021 — Double pointer output:

int x = 10, *p = &x, **pp = &p;
**pp = 20;
printf("%d", x);  // x changed via double pointer

Output: 20

10. Common Mistakes

  • sizeof(array) vs sizeof(pointer): After passing array to function, you get pointer — sizeof gives 4 or 8, not the full array size. Cannot recover array size inside function without passing it explicitly.
  • int *p[5] vs int (*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 **a with malloc gives non-contiguous rows — cannot pass to functions expecting int 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.

Leave a Comment