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

Updated 26 Sep 20265 min read

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.

Memory map with score at illustrative address 1000 holding 42 then 57, and pointer p at address 2000 storing 1000.

Declare, initialise and dereference a pointer safely

Writing through the pointer changes the aliased object:

c
#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:

Code
before: 42 42
after: 57 57

p 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:

c
#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:

Code
11 17 3 14 8

Initially, 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.

c
#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:

Code
before: 14
after first call: 20
after second call: 20

On 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 because p is uninitialised. int *p = NULL; creates a known no-object state, but dereferencing it is still invalid. Give p a valid address or guard access with if (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 NULL for later checks. Assigning NULL does 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. In int *p, value;, only p is a pointer.

A safe allocation pattern checks the allocation before access:

c
#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:

c
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:

c
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

  1. For int n = 8; int *p = &n; *p += 5;, what is n? Answer: n == 13, because 8 + 5 = 13.

  2. For int a[] = {5, 9, 12, 20}; int *p = a + 1;, find *p and *(p + 2). Answer: *p == 9. Two more elements from a[1] reaches a[3], so *(p + 2) == 20.

  3. Repair int *p; *p = 10;. Answer: use int value = 10; int *p = &value;. If no object is available yet, use int *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.