Pointers and pointer arithmetic
A pointer is a variable that holds an address. Learn what &, * and p + 1 really do (p + 1 moves a whole element, not a byte), how *p++ and (*p)++ differ, and which pointer mistakes are undefined behaviour.
Why it exists
A variable name gives access to one object. Often a program needs to refer to an object indirectly: let a function change its caller's variable, walk along an array, build a list whose cells point to each other, ask the operating system for a block of memory whose size is not known until the program runs. All of these need a value that means "the object over there". That value is an address, and a variable that holds one is a pointer.
A pointer is also the machine's own way of reaching memory. The CPU's indirect addressing works exactly like *p: the instruction names a register, and the register holds the address of the data. C puts that mechanism in your hands, which is why it is both powerful and the source of most C bugs.
Prerequisites
Precise definitions
- Pointer. An object whose value is the address of another object (or function).
int *p;declarespas a pointer to int. The typeint *records what kind of objectpis supposed to point at. - Address-of
&.&xis the address ofx, of typeint *ifxis anint. - Dereference
*.*pis the object thatppoints at. It can be read (y = *p) or written (*p = 5). Using*in a declaration (int *p) is a different use of the same symbol: it says "p is a pointer". - Pointer to pointer.
int **ppholds the address of anint *.**ppfollows two arrows. The animation below does this. - The null pointer. A null pointer is a valid value that points at no object. It is written with a null pointer constant: an integer constant expression with value 0 (such as
0) or that expression cast tovoid *.NULLis a macro for one of these. A variable that merely holds 0 (int z = 0; p = z;) is not a null pointer constant. Comparing a pointer withNULLis fine. Dereferencing a null pointer is undefined behaviour. - Size of a pointer.
sizeof(int *)is the size of an address: 8 bytes on a typical 64-bit machine and 4 on a 32-bit one. On such platforms it does not depend on the pointee:int *,char *anddouble *are all the same size. (The C standard itself only requires thatvoid *andchar *match each other; the equal sizes are a property of common platforms, and GATE assumes them.) What differs issizeof(*p), the size of the pointee. - Why the pointer type matters. The type tells the compiler (1) how many bytes to read or write for
*p, and (2) how farp + 1moves. - Pointer arithmetic (C11 6.5.6). For
T *p:p + nis the address . It moves n elements, not n bytes.p++andp--likewise.p - q(both pointing into the same array) is the number of elements between them, of typeptrdiff_t.p + nis valid only ifppoints into an array (a single object counts as an array of one element) and the result is inside the array or exactly one past the last element.nmay be negative. The one-past pointer may be compared and subtracted, but not dereferenced (forming&a[5]for a 5-element array is still allowed, because&and*cancel). Any other result is undefined behaviour, even if you never dereference it.- Relational comparison (
<,<=, ...) is defined only between pointers into the same object (the same array, or members of the same struct).==and!=work between pointers of compatible type, even into different arrays, though a one-past-the-end pointer may happen to equal the address of a neighbouring object. - Adding two pointers, or multiplying or dividing a pointer, is an error.
void *. A pointer with no type. It can hold any object pointer and converts to and from other object-pointer types without a cast in C (qualifiers such asconstmust be preserved), but it cannot be dereferenced. Standard C does not allow arithmetic on it (gcc allows it as an extension and treats the size as 1).constand pointers. Read the declaration from the name outwards, right to left.
| Declaration | p is... | *p = 1; | p = &y; |
|---|---|---|---|
int *p | pointer to int | allowed | allowed |
const int *p (same as int const *p) | pointer to const int | error | allowed |
int *const p | const pointer to int | allowed | error |
const int *const p | const pointer to const int | error | error |
- Bad pointers. A wild (uninitialised) pointer holds an indeterminate value. A dangling pointer holds the address of an object whose lifetime has ended (a returned local, a freed block); in C11 such a pointer's value is indeterminate, so even using it without dereferencing is not allowed. A null pointer is a perfectly good value, but it points at no object and must not be dereferenced. Dereferencing any of the three is undefined behaviour.
How it works
Following arrows
Step through this real run. Watch three things. First, q = p copies an address, so after it there are two arrows to the same x. Second, p = &y changes where p points and leaves x alone, while *p = ... changes the pointee. Third, pp is a pointer to the pointer p.
A pointer holds an address
Real run, real addressesC source
Stack
xint
yint
Amber = changed in this step. Hover a cell for its full address. Arrows show where a pointer points; the small tag under a cell names the pointers aimed at it.
Step 1
Two ordinary ints. Nothing points anywhere yet.
The two assignments are the most confusing pair in the subject:
| Statement | What it changes |
|---|---|
p = q; | the pointer p: it now points where q points |
*p = *q; | the object p points at: it receives a copy of the value of the object q points at |
Moving through an array
In the next run p walks along an array. Look at the address of p in the cell. Each p++ adds 4, because p points to int. q - p is 3 although the addresses differ by 12: the subtraction divides by the element size.
Pointer arithmetic moves in whole elements
Real run, real addressesC source
Stack
aint
ppointer
Amber = changed in this step. Hover a cell for its full address. Arrows show where a pointer points; the small tag under a cell names the pointers aimed at it.
Step 1
An array name in an expression becomes a pointer to its first element, so p points at a[0].
*p++, (*p)++, *++p
Postfix ++ binds tighter than unary *, and prefix ++ and * group right to left. So:
| Expression | Parsed as | Value of the expression | Effect |
|---|---|---|---|
*p++ | *(p++) | the old target, *p before the move | p advances one element |
(*p)++ | (*p)++ | the old value of *p | the pointee is incremented, p stays |
*++p | *(++p) | the target after the move | p advances one element |
++*p | ++(*p) | the new value of *p | the pointee is incremented |
Worked examples
EX 1Address, dereference, and the same object seen twice
Easy#include <stdio.h>
int main(void) {
int x = 10;
int *p = &x;
*p = 11;
printf("%d %d %d\n", x, *p, p == &x);
return 0;
}Output:
11 11 1Reading it. *p = 11 writes into x itself, so x and *p are the same object and both read 11. p == &x compares two addresses and is true (1).
EX 2A pointer to a pointer
Medium#include <stdio.h>
int main(void) {
int x = 10, y = 20;
int *p = &x;
int **pp = &p;
*pp = &y;
**pp = *p + 1;
printf("%d %d %d\n", x, y, *p);
return 0;
}Output:
10 21 21Reading it. *pp is p itself, so *pp = &y re-aims p at y. Now **pp is *p, which is y. The right side reads *p, which is 20, adds 1, and **pp = 21 stores it into y. x was never touched.
EX 3Pointer difference counts elements
Medium#include <stdio.h>
int main(void) {
int a[5] = {10, 20, 30, 40, 50};
int *p = a;
p++;
int *q = p + 3;
printf("%d %td %d\n", *q, q - p, *(q - 1));
return 0;
}Output:
50 3 40Reading it. After p++, p points at a[1]. p + 3 is a[4], which holds 50. q - p is 3 elements, even though the two addresses are 12 bytes apart. *(q - 1) is a[3], which holds 40.
EX 4*p++, (*p)++ and *++p in a row
GATE level#include <stdio.h>
int main(void) {
int a[4] = {10, 20, 30, 40};
int *p = a;
int x = *p++;
int y = (*p)++;
int z = *++p;
printf("%d %d %d %d %td\n", x, y, z, a[1], p - a);
return 0;
}Output:
10 20 30 21 2Step by step. Start with p at a[0].
x = *p++: take the old target,a[0]= 10, then moveptoa[1]. So .y = (*p)++: the pointee isa[1]= 20. The expression yields 20 and thena[1]becomes 21.pdoes not move. So .z = *++p: movepfirst, toa[2], then read it. So .
At the end a[1] is 21 and p - a is 2.
EX 5sizeof of a pointer versus its target
GATE level#include <stdio.h>
int main(void) {
int *ip;
char *cp;
double *dp;
printf("%zu %zu %zu %zu %zu %zu\n", sizeof ip, sizeof cp, sizeof dp,
sizeof *ip, sizeof *cp, sizeof *dp);
return 0;
}Output (64-bit gcc):
8 8 8 4 1 8Reading it. On this platform all pointers are the same size, 8 bytes. The sizes of the targets are 4, 1 and 8. Note that sizeof *ip does not dereference ip (the operand of sizeof is not evaluated), so the uninitialised pointer causes no problem here. This is a favourite check of whether you can separate "the pointer" from "what it points at".
EX 6Looking at the bytes of an int through a char pointer
GATE level#include <stdio.h>
int main(void) {
unsigned int x = 0x12345678;
unsigned char *c = (unsigned char *)&x;
printf("%x %x\n", c[0], c[3]);
return 0;
}Output on a little-endian machine such as x86:
78 12Reading it. c points at the first (lowest-address) byte of x. On a little-endian machine the least significant byte, 0x78, is stored first, so c[0] is 0x78 and c[3] is 0x12. A big-endian machine would print 12 78. The order is a property of the machine, not of C, so a GATE question must state it (or ask which machine type produces a given output). Reading an object's bytes through unsigned char * is explicitly allowed by C.
Cheat box
Cheat box
&xaddress ofx.*pthe objectppoints at.p = qcopies an address;*p = *qcopies a value.p + nmoves bytes.p - qis in elements. Only inside an array or one past its end.- Do not dereference:
NULL, a wild pointer, a dangling pointer, or the one-past-the-end pointer. *p++read old target, then advancep.(*p)++increment the pointee.*++padvance, then read.- On typical platforms all pointers are the same size (8 bytes on 64-bit).
sizeof *pis the pointee size. const int *p: cannot change*p.int *const p: cannot changep.int *p, q;makespa pointer andqa plainint.- Byte order of an int seen through an
unsigned char *is machine-dependent.
Traps and GATE patterns
Connections
Unlocks
Across subjects
Pointers and arrays are tied together: an array name decays to a pointer in most expressions, and a[i] is *(a + i). The arrays, strings and pointers page works through the consequences. Where a pointer variable and its target live is on the memory layout page. Dynamic memory is the main source of pointers whose target is not a named variable. A CPU's indirect addressing mode is the hardware version of *p.
PYQs
8 previous year questions from your book. I re-solved each one myself (8 by running or simulating, 0 by reasoning only); the book's key is shown next to my answer. All PYQs
Consider this C code to swap two integers and these
five statements:
void swap(int *px, int *py) {
*px = *px - *py
*py = *px + *py;
*px = *px - *py;
}
S1: will generate a compilation error
S2: may generate a segmentation fault at runtime
depending on the arguments passed
S3: correctly implements the swap procedure for all
input pointers referring to integers stored in memory
locations accessible to the process
S4: implements the swap procedure correctly for
some but not all valid input pointers
S5: may add or subtract integers and pointers.- (a)S1
- (b)S2 and S3
- (c)S2 and S4
- (d)S2 and S5
Consider the following ANSI-C program.
#include <stdio.h>
int main() {
int *ptr, a, b, c;
a = 5;
b = 11;
c = 20;
ptr = &a;
*ptr = c;
ptr = &c;
a = *(&b);
c = *ptr - a;
printf("%d", c);
return 0;
}
The output of this program is __________. (answer
in integer)Consider the following C program:
#include < stdio.h >
void foo(int *p, int x) {
*p = x;
}
int main() {
int *z;
int a = 20, b = 25;
z = &a;
foo(z, b);
printf("%d", a);
return 0;
}
The output of the given C program is__________ .
(Answer in integer)#include<stdio.h>
void fun1(char *s1, char *s2) {
char *tmp;
tmp = s1;
s1 = s2;
s2 = tmp;
}
void fun2(char **s2, char **s2) {
char *tmp;
tmp = *s1;
*s1 = *s2;
*s2 = tmp;
}
int main () {
char *str1 = "Hi", *str2 = "Bye";
fun1(str1, str2);
printf("%s %s", str1, str2);
fun2(&str1, &str2);
printf("%s %s", str1, str2);
return 0;
}
The output of the program above is- (a)Hi ByeBye Hi
- (b)Hi Bye Hi Bye
- (c)Bye Hi Hi Bye
- (d)Bye Hi Bye Hi
Consider the following function implemented in
void printxy (int x, int y) {
int *ptr;
x = 0;
ptr = &x;
y = *ptr;
*ptr = 1;
printf("%d,%d",x,y);
}
The output of invoking printxy(1, 1) is_____- (a)0, 0
- (b)0 ,1
- (c)1, 0
- (d)1, 1
#include<stdio.h>
void mystery(int *ptra, int *ptrb) {
int *temp;
temp = ptrb;
ptrb = ptra;
ptra = temp;
}
int main() {
int a=2016, b=0, c=4, d=42;
mystery(&a, &b);
if (a < c)
mystery(&c, &a);
mystery(&a, &d);
printf("%d\n", a);
}
The output of the program is ________.Consider the following C program.
# include<stdio.h>
int main( )
{
static int a[] = {10, 20, 30, 40, 50};
static int *p[] = {a, a+3, a+4, a+1, a+2};
int **ptr = p;
ptr++;
printf("%d%d", ptr - p, **ptr};
}
The output of the program is _________Consider the following program in C language:
#include <stdio.h>
main()
{
int i;
int *pi = &i;
scanf("%d", pi);
printf("%d\n", i+5);
}
Which one of the following statements is TRUE?- (a)Compilation fails.
- (b)Execution results in a run-time error.
- (c)On execution, the value printed is 5 more than the address of variable i.
- (d)On execution, the value printed is 5 more than the integer value entered.
Self-check
Question 1
What is the type of q in int *p, q;, and what does sizeof(p) == sizeof(q) evaluate to on a 64-bit machine?
Show answerHide
q is a plain int. sizeof(p) is 8 and sizeof(q) is 4, so the comparison is false (0).
Question 2
A pointer holds the address 2000 on a machine where int is 4 bytes and double is 8 bytes. What is the address after + 3 if the pointer is (a) int *, (b) char *, (c) double *?
Show answerHide
(a) . (b) . (c) .
Question 3
What does this print?
#include <stdio.h>
int main(void) {
int a[4] = {1, 2, 3, 4};
int *p = a + 1;
printf("%d %d %d\n", *p + 1, *(p + 1), p[-1]);
return 0;
}Show answerHide
*p + 1 is the value 2 plus 1, which is 3. *(p + 1) is the next element, 3. p[-1] is *(p - 1), which is a[0] = 1. Negative indices through a pointer are fine as long as the result stays inside the array.
3 3 1Question 4
Which of these are undefined behaviour? (a) *p where p is NULL. (b) Forming a + 5 for int a[5] and comparing it with a + 2. (c) Reading *(a + 5) for int a[5]. (d) int *p; *p = 1;.
Show answerHide
(a) undefined. (b) defined: a + 5 is one past the end, which may be formed and compared. (c) undefined: you may not dereference the one-past pointer. (d) undefined: p is uninitialised.
Question 5
Given const int *p = &x; and int y;, which of *p = 3; and p = &y; compile?
Show answerHide
p = &y; compiles (the pointer itself may change). *p = 3; is an error, because p points to a const int. To forbid p = &y you would need int *const p.
Revision card
One-screen revision
- A pointer holds an address.
&takes one,*follows one.p = qcopies an arrow;*p = *qcopies a value. p + nmoves bytes.p - qcounts elements. Valid only inside an array or one past it.*p++: old target, then advance.(*p)++: increment the pointee.*++p: advance, then read.- On typical platforms all pointers are one size;
sizeof *pis the pointee size. const int *pguards the pointee.int *const pguards the pointer.- Undefined: dereferencing NULL, wild, dangling or one-past pointers; using a dangling pointer's value at all;
<or-on pointers into different arrays. int *p, q;: onlypis a pointer.
References
- ·Kernighan and Ritchie, The C Programming Language, 2nd ed., Sections 5.1 Pointers and Addresses, 5.2 Pointers and Function Arguments, 5.3 Pointers and Arrays, 5.4 Address Arithmetic, 5.6 Pointer Arrays, 5.10 Pointers vs. Multi-dimensional Arrays
- ·ISO/IEC 9899:2011 (C11), Sections 6.2.4 Storage durations of objects, 6.2.5 Types (pointer types), 6.3.2.3 Pointers, 6.5.2.4 Postfix increment and decrement operators, 6.5.3.1 Prefix increment and decrement operators, 6.5.3.2 Address and indirection operators, 6.5.6 Additive operators, 6.5.8 Relational operators, 6.5.9 Equality operators, 6.7.3 Type qualifiers, 6.7.6.1 Pointer declarators
Verification
Both animations replay real runs of instrumented C programs: every address and pointer target is read from memory at each step, and the test suite checks the 4-byte step of p++, the 12-byte gap behind q - p == 3, and the double-indirection result. All C programs on this page are compiled with -Wall -Wextra -Werror and run by npm run verify:c. The byte-order example depends on a little-endian machine, which the page says. The textbook section numbers were written from memory and have NOT been checked against the books. The prose and the GATE-pattern claims have NOT yet had a human textbook check (the independent second pass is described at the end of these notes). SECOND PASS (2026-11-02, done by the assistant, not a human check): every Self-check question was solved before the printed answer was read and all agreed; the definitions, worked examples, cheat box and traps were read claim by claim and the numbers re-derived by hand (long C programs were not re-read line by line: they are compiled and compared by npm run verify:c, and their printed outputs were checked against the prose). No defect found. Status raised to self-checked; verified needs a textbook or human check.
last verified: never
My notes (saved in this browser)