Introduction

A pointer is a value that holds the address of an object (or function), together with the type of what it points to. Pointers let a function modify its caller's variables, let programs build linked structures such as linked lists and trees, give access to memory obtained at run time with malloc, and underlie the way C handles arrays and strings.

Three rules carry most of this note: what & and * do, how pointer arithmetic is scaled, and how *, ++ and const combine.

Conventions: C11, LP64 (every object pointer is 8 bytes; int is 4 bytes). Addresses in examples are illustrative and written in decimal unless marked hexadecimal.

Prerequisites

Core concepts

Addresses, & and *

Every object occupies one or more bytes, and every byte has an address. For an object a:

  • &a (address-of) yields a pointer to a. Its type is "pointer to the type of a".
  • If p points to a, then *p (indirection, dereference) designates a itself: it is an lvalue, so *p = 15 stores into a.
  • & and * are inverses: *&a is a, and &*p is p.
C
int a = 10;
int *p = &a;      /* p holds the address of a */
int **q = &p;     /* q holds the address of p */
*p = 15;          /* a is now 15 */
**q = 20;         /* *q is p, **q is a: a is now 20 */

In a declaration, * belongs to the declarator, not to the type: int *p, q; declares a pointer p and an int q. Write int *p, *q; for two pointers.

Box-and-arrow model of a, p and q
Figure 1. Box-and-arrow model: the int a (value 20) at an illustrative address, the pointer p holding that address, and the pointer-to-pointer q holding the address of p; *q follows one arrow, **q follows two.

Pointer types and sizeof

The pointed-to type tells the compiler how many bytes to read on dereference and how far to step in arithmetic. All object pointers on LP64 are 8 bytes: sizeof(int *), sizeof(char *) and sizeof(int **) are all 8, while sizeof *p for int *p is 4.

The null pointer and void *

  • A null pointer compares unequal to the address of every object. The integer constant 0 and the macro NULL both convert to a null pointer, so p == NULL, p == 0 and !p all test for it. Dereferencing a null pointer is undefined behaviour.
  • void * is a generic object pointer. Any object pointer converts to void * and back without loss, implicitly in C (no cast needed): void *v = &n; int *ip = v;. A void * cannot be dereferenced or used in arithmetic (gcc allows arithmetic as an extension; standard C does not).

Pointer arithmetic

For a pointer p to an element of an array of type T:

address(p+k)=address(p)+k⋅sizeof(T).\text{address}(p + k) = \text{address}(p) + k \cdot \texttt{sizeof}(T).

So if an int array starts at address 1000, p + 3 points to address 1012; for char it would be 1003 and for double 1024. The allowed operations are:

OperationResultCondition
p + k, p - k, p++, p--pointerresult must stay within the same array or one past its end
p - qptrdiff_t, number of elements between themsame array or one-past, and the mathematical difference is representable in ptrdiff_t
p < q, p == q and so on0 or 1relational: only C11-defined object/aggregate cases; equality: valid operand types required, but not restricted to one array
p + q, p * 2, p / qnot alloweddoes not compile

p[k] is defined as *(p + k); this is the link between pointers and arrays (Arrays and Their Relation to Pointers).

Combining * with ++

Postfix ++ binds tighter than unary *; prefix ++ and * are both unary and group right to left.

ExpressionMeaningEffect
*p++*(p++)yields the value at the old p, then p moves to the next element
(*p)++increment the pointeeyields the old pointee value; p unchanged
++*p++(*p)increments the pointee, yields the new value
*++p*(++p)moves p first, then yields the new pointee

const and pointers

Read declarations from the name outward: const applies to what is immediately to its left, or to the right if nothing is on its left.

Declaration*p = 5p = &bRead as
const int *p (same as int const *p)errorallowedpointer to const int
int *const p = &aallowederrorconst pointer to int
const int *const p = &aerrorerrorconst pointer to const int

A pointer to const only promises that the object will not be modified through that pointer; the object itself may still change by other means.

Dangerous pointers

  • Uninitialised (wild) pointer: an automatic int *p; holds an indeterminate value; *p = 1 is undefined behaviour.
  • Dangling pointer: points to an object whose lifetime has ended, such as a local variable of a function that has returned, or a block that has been freed.
  • Null pointer: valid to hold and compare, invalid to dereference.

How it works: pointers as function arguments

C passes arguments by value (Functions and Parameter Passing), so a pointer parameter is a copy of the caller's pointer. Through the copy the function can change the pointee; it cannot change the caller's pointer variable. To change a pointer, pass the address of the pointer:

C
void wrong_set(int *p, int *target) { p = target; }       /* changes the copy */
void set_to(int **pp, int *target)  { *pp = target; }     /* changes the caller's p */

int a = 1, b = 2, *p = &a;
wrong_set(p, &b);   /* *p is still 1 */
set_to(&p, &b);     /* *p is now 2 */

This is exactly why linked-list insertion at the head takes struct node **head.

Worked examples

All outputs below are for C11 with gcc on x86-64 Linux (LP64).

Example 1 (levels of indirection). With int a = 10; int *p = &a; int **q = &p;: *p, **q and *p + 1 are 10, 10 and 11. After **q = 20, a is 20. After int b = 99; *q = &b;, p points to b, so *p is 99 while a is still 20.

Example 2 (scaling). int arr[5] = {10, 20, 30, 40, 50}; int *p = arr; Then *(p + 2) is 30, p[4] is 50, *(arr + 1) is 20. p + 3 is 12 bytes past p, a char * advanced by 3 is 3 bytes past, and a double * advanced by 3 is 24 bytes past. With int *r = &arr[4];, r - p is 4 (elements, not bytes).

Example 3 (the four */++ forms). Start with int a[4] = {10, 20, 30, 40}; int *p = a; and run in order:

StatementxWhere p pointsArray afterwards
x = *p++;10a[1]10 20 30 40
x = (*p)++;20a[1]10 21 30 40
x = ++*p;22a[1]10 22 30 40
x = *++p;30a[2]10 22 30 40

Example 4 (const). const int *p = &a; *p = 5; fails with "assignment of read-only location". int *const p = &a; p = &b; fails with "assignment of read-only variable".

Equality operand types. For pointer equality, use pointers to compatible (possibly differently qualified) types, an object/incomplete pointer paired with a suitably qualified void *, or a pointer paired with a null pointer constant, as permitted by C11 6.5.9. “Different arrays” does not by itself forbid a validly typed equality test. Relational comparisons follow the separate array/structure/union cases of 6.5.8.

Important properties and observations

  • int *p, q; makes only p a pointer.
  • Pointer arithmetic counts in elements of the pointed-to type; p - q is in elements.
  • p[i] is *(p + i), and also i[p], since addition commutes.
  • All object pointers have the same size on common systems (8 bytes on LP64), whatever they point to.
  • A pointer may legally point one past the end of an array, but must not be dereferenced there.
  • void * converts implicitly to and from other object pointer types in C.
  • Changing a caller's pointer needs a pointer to that pointer.

GATE examination perspective

  • Multi-level indirection: draw boxes and arrows; each * follows one arrow.
  • Address arithmetic with a given base address: multiply by the element size the question states.
  • *p++ family: use the table above; the usual pitfall is treating *p++ as incrementing the pointee.
  • const placement questions: which assignments compile.
  • Swap and pointer-to-pointer questions: decide whether the caller's variable, pointer or pointee changes.

Summary

Sources

  1. ISO/IEC 9899:2011 (C11), committee draft N1570, (consulted 2026-11-09), Section 6.3.2.3 (pointers, null pointer constants, void * conversions), 6.5.2.1 (array subscripting), 6.5.3.2 (address and indirection operators), 6.5.6 (additive operators, pointer arithmetic), 6.5.8 and 6.5.9 (relational and equality operators), 6.7.3 (type qualifiers), 6.7.6.1 (pointer declarators).
  2. B. W. Kernighan and D. M. Ritchie, The C Programming Language, 2nd ed., Prentice Hall, 1988: Chapter 5 (pointers and arrays), Sections 5.1 to 5.4.

Next steps