Declaring one
#include <stdio.h>
int main(void)
{
int marks = 91;
int *p; /* p can hold the address of an int */
p = &marks; /* now it does */
printf("marks = %d\n", marks);
printf("p = %p\n", (void *)p);
printf("*p = %d\n", *p);
printf("&marks = %p\n", (void *)&marks);
return 0;
}
p and &marks print the same number. *p and marks print the same value. Those two facts are what "p points to marks" means.
Read the declaration right to left: int p is "p is a pointer to an int". The type is int , not int.
Writing through a pointer
#include <stdio.h>
int main(void)
{
int marks = 91;
int *p = &marks;
*p = 100; /* changes marks itself */
printf("marks = %d\n", marks); /* 100 */
(*p)++; /* marks becomes 101 */
printf("marks = %d\n", marks);
return 0;
}
*p = 100 does not change p. It changes what p points at. p still holds the same address.
(*p)++ and *p++ are different. (*p)++ increments the value at the address. *p++ increments the pointer — moves it 4 bytes along — and gives back the old value. The brackets are not optional. This is a standard exam question.
The pointer type must match
#include <stdio.h>
int main(void)
{
int n = 1078530011;
int *ip = &n;
char *cp = (char *)&n; /* same address, different type */
printf("as int : %d\n", *ip);
printf("as char : %d\n", *cp);
return 0;
}
Both pointers hold the same address. *ip reads 4 bytes and interprets them as an int. cp reads 1 byte. The type of a pointer is not decoration — it decides how many bytes to read and how to interpret them. That is also why p + 1 moves 4 bytes for an int and 1 byte for a char *.
Without the cast, char *cp = &n; is a type error, and gcc will tell you. Never silence such a warning with a cast unless you know exactly why.
NULL: a pointer that points nowhere
An uninitialised pointer holds garbage. Dereferencing it writes to a random address, and random addresses are where the worst bugs live.
#include <stdio.h>
int main(void)
{
int *bad; /* garbage value */
int *good = NULL; /* deliberately nothing */
if (good == NULL)
printf("good points nowhere, and I know it\n");
/* *bad = 5; <-- would probably crash, and might not */
(void)bad;
return 0;
}
NULL is defined in stdio.h and stdlib.h as address zero. No real data ever lives at address zero, so the operating system marks that page as forbidden. Dereferencing NULL therefore gives you an immediate, reliable segmentation fault — a crash. That is a feature. A crash on line 10 is far kinder than silent corruption that surfaces on line 400.
Two habits that prevent most pointer crashes. One: initialise every pointer at declaration, to a real address or to NULL. Two: check for NULL before dereferencing any pointer you did not create yourself in the line above.
Segmentation fault, explained
Your process is given specific regions of memory. Touching anything outside them makes the CPU's memory management unit raise a fault, and the OS kills your program with the message Segmentation fault (core dumped).
The four ways students produce one:
- Dereferencing
NULL— often amallocwhose result was not checked. - Dereferencing an uninitialised pointer.
- Forgetting the
&inscanf("%d", n). - Using a pointer after the memory it pointed to was freed or went out of scope.
A segfault is not a compiler error. The code compiles fine. It is a runtime crash, and the compiler cannot always see it coming.
Pointer to pointer
#include <stdio.h>
int main(void)
{
int x = 5;
int *p = &x;
int **pp = &p;
printf("x = %d\n", x); /* 5 */
printf("*p = %d\n", *p); /* 5 */
printf("**pp = %d\n", **pp); /* 5 */
**pp = 42;
printf("x = %d\n", x); /* 42 */
return 0;
}
pp holds the address of p, which holds the address of x. One * gets you to p. Two get you to x. You will meet this for real when a function needs to change a caller's pointer, not just the value it points to.
const and pointers
#include <stdio.h>
int main(void)
{
int a = 1, b = 2;
const int *p = &a; /* cannot change the value through p */
/* *p = 10; */ /* error */
p = &b; /* but p itself can move */
int * const q = &a; /* q cannot move */
*q = 10; /* but the value can change */
/* q = &b; */ /* error */
printf("a = %d, b = %d\n", a, b); /* a = 10, b = 2 */
return 0;
}
Read it right to left again. const int p is "pointer to const int". int const q is "const pointer to int". Function parameters like size_t strlen(const char *s) use the first form to promise you the function will not modify your string.
A worked example
#include <stdio.h>
void swap(int *a, int *b)
{
int temp = *a;
*a = *b;
*b = temp;
}
int main(void)
{
int x = 3, y = 8;
printf("before: x = %d, y = %d\n", x, y);
swap(&x, &y);
printf("after : x = %d, y = %d\n", x, y);
return 0;
}
Output: before: x = 3, y = 8 then after : x = 8, y = 3.
Write the same function taking plain int a, int b and it does nothing at all, because it swaps two copies that die at the closing brace. That comparison is the clearest single demonstration of why pointers exist, and it is worth typing both versions out.