Pointers in C: Addresses, Arrays and Functions with Worked Examples
Build a practical pointer model with runnable C programs for dereferencing, arrays, swapping, dynamic memory and common tracing questions.
KnowledgeGate Team
Exam prep & CS education

Pointers usually become confusing at three symbols: score is an integer, &score is its address, and int *p can hold that address. A one-level memory model connects safe dereferencing to bounded array traversal and in-place function updates. Use Pointers in C for GATE: Memory Diagrams and Output Traces for double-pointer traces and failed swaps, and Arrays and Pointers in C: Memory, Arithmetic, Traps for decay, two-dimensional address arithmetic and declaration traps. The C Language course places these skills in the wider language sequence.
What a pointer stores in C
Start with int score = 42; int *p = &score;. The object score holds 42, &score identifies its location, p stores that location, and *p accesses the integer there. In int *p, the pointed-to type is int. The pointer p is not the integer 42; it is a separate object.
Suppose score occupies illustrative address 1000 and p occupies 2000. Their cells contain 42 and 1000 respectively. After *p = 57;, both score and *p evaluate to 57, while p still contains 1000.

Declare, initialise and dereference a pointer safely
Writing through the pointer changes the aliased object:
#include <stdio.h>
int main(void) {
int score = 42;
int *p = &score;
printf("before: %d %d\n", score, *p);
*p = 57;
printf("after: %d %d\n", score, *p);
return 0;
}The output is:
before: 42 42
after: 57 57p means the stored address; *p means the object reached through it. Writing through *p changes score because both expressions reach the same object. To print the address itself, use printf("%p", (void *)p);.
Initialise a pointer with a live object's address or with NULL. Dereference it only when it refers to a live object of the right type.
Pointer arithmetic within array bounds, fully worked
Pointer arithmetic is defined within one array object and its one-past position. samples + 5 may be formed for a five-element array, but it must move back into the array before dereferencing:
#include <stdio.h>
int main(void) {
int samples[5] = {8, 14, 3, 17, 11};
int *cursor = samples + 5;
while (cursor != samples) {
--cursor;
printf("%d%c", *cursor, cursor == samples ? '\n' : ' ');
}
return 0;
}The output is:
11 17 3 14 8Initially, cursor - samples is 5, so cursor is one-past and is not dereferenced. Each --cursor moves to indices 4, 3, 2, 1 and 0 before *cursor reads 11, 17, 3, 14 and 8. The final conditional prints a newline when the cursor reaches index 0.
Each step advances by one int, not one raw byte. In most expressions, samples converts to a pointer to its first element, but the array object and the pointer variable remain different things. Pointer subtraction and comparisons are meaningful here because both pointers refer into the same array.
Pass an address to a function
C passes arguments by value. Changing an ordinary int parameter changes only a copy. A copied pointer value still gives the function a route to the caller's object.
#include <stdio.h>
void raise_floor(int *value, int floor) {
if (*value < floor) {
*value = floor;
}
}
int main(void) {
int reading = 14;
printf("before: %d\n", reading);
raise_floor(&reading, 20);
printf("after first call: %d\n", reading);
raise_floor(&reading, 18);
printf("after second call: %d\n", reading);
return 0;
}The output is:
before: 14
after first call: 20
after second call: 20On the first call, value stores the address of reading, so 14 < 20 writes 20 into the caller's object. On the second call, 20 < 18 is false and the value stays 20. The pointer itself is still passed by value; dereferencing it enables output parameters and in-place updates.
Common pointer errors and the repair for each
Wild or null pointer:
int *p; printf("%d", *p);is invalid becausepis uninitialised.int *p = NULL;creates a known no-object state, but dereferencing it is still invalid. Givepa valid address or guard access withif (p != NULL) { ... }.Dangling pointer: after an object is freed, a pointer to it no longer identifies a live object. Free once, then set the pointer to
NULLfor later checks. AssigningNULLdoes not free memory.Lifetime, type or bounds error: do not return
&local. Do not step outside an array except to form its one-past pointer, and never dereference it. Inint *p, value;, onlypis a pointer.
A safe allocation pattern checks the allocation before access:
#include <stdlib.h>
int *p = malloc(3 * sizeof *p);
if (p != NULL) {
p[0] = 5;
p[1] = 10;
p[2] = 15;
free(p);
p = NULL;
}Reading *p after free(p) is invalid. Setting p = NULL prevents this variable from remaining dangling, but free releases the allocation.
How exams and interviews test pointer tracing
Trace one level at a time in a pointer-to-pointer expression:
int x = 5;
int *p = &x;
int **pp = &p;
**pp += 4;
printf("%d %d\n", x, *p);pp leads to p, and p leads to x. The update computes 5 + 4 = 9, so the program prints 9 9.
Precedence creates another common trace:
int a[3] = {3, 6, 9};
int *p = a;
int first = *p++;
printf("%d %d\n", first, *p);*p++ parses as *(p++). It reads 3 through the original pointer, then advances p to a[1], whose value is 6. The output is 3 6. Avoid expressions such as *p + *p++, which mix access and modification in a way that can make behaviour undefined.
Recurring tasks ask you to predict output, follow aliases after a write, distinguish p, *p and &p, or find an invalid dereference. Pointer Basics practice questions reinforce these traces. For broader interview drills, use the Coding for Placements course.
Practice exercises, short version and next step
For
int n = 8; int *p = &n; *p += 5;, what isn? Answer:n == 13, because8 + 5 = 13.For
int a[] = {5, 9, 12, 20}; int *p = a + 1;, find*pand*(p + 2). Answer:*p == 9. Two more elements froma[1]reachesa[3], so*(p + 2) == 20.Repair
int *p; *p = 10;. Answer: useint value = 10; int *p = &value;. If no object is available yet, useint *p = NULL;and do not dereference it.
A pointer stores an address. & obtains an address, and * follows a valid pointer. Array stepping stays within one array and moves by elements. The C Language course develops the full sequence, while the wider Coding and DSA learning path adds data structures. Compile the three runnable programs with warnings enabled, predict each line first, then change one value and trace it again.
Keep learning

Functions in C: Syntax, Parameters and Worked Examples
Learn how C declarations, calls, and definitions fit together. Compile two complete programs, follow their values, and trace composition and recursion.

Smart Pointers in C++: unique_ptr, shared_ptr and weak_ptr with Worked Examples
Learn who owns a C++ object, when it is destroyed, and how to choose among unique_ptr, shared_ptr, weak_ptr and a borrowed raw pointer.

Pointers and Arrays in C: Decay, Indexing and Worked Examples
Separate arrays from pointers with runnable traces covering indexing, one-past boundaries, function parameters, two-dimensional arrays and declaration traps.

Function Pointers in C: Declarations, Callbacks and Runnable Examples
Learn to read function-pointer declarations, build a callback dispatch table, guard invalid selections, and write a safe qsort comparator in C.