A pointer is a variable that stores the memory address of another variable. This indirection is the foundation of C's power — it enables dynamic data structures, efficient function calls, and direct hardware interaction.
pointer_basics.c
C
1
#include <stdio.h>
2
3
int main(void) {
4
int x = 42;
5
int *p = &x; /* p holds the address of x */
6
printf("x: %d\n", x);
7
printf("&x: %p\n", (void *)&x);
8
printf("p: %p\n", (void *)p);
9
printf("*p: %d\n", *p);
10
return 0;
11
}
Concept
Symbol
Description
Address-of
&x
Returns the memory address of x
Dereference
*p
Accesses the value at the address in p
Declaration
int *p
Declares p as a pointer to int
Declaration and Initialization
The asterisk (*) is part of the variable name, not the type. Each pointer in a multi-declaration must be prefixed with *.
declaration.c
C
1
int *p; /* pointer to int */
2
int *a, *b, *c; /* all pointers to int */
3
int *d, e; /* d is a pointer, e is plain int! */
4
5
int x = 10;
6
int *p = &x; /* p points to x */
7
8
int *q; /* wild pointer: uninitialized (DANGEROUS) */
9
10
int *r = NULL; /* safe: r points to nothing */
⚠
warning
An uninitialized pointer is a "wild pointer". Dereferencing it is undefined behavior. Always initialize pointers to a valid address or NULL.
The NULL Pointer
NULL represents a pointer pointing to nothing. Dereferencing NULL typically causes a segmentation fault. Always check for NULL before dereferencing.
null_pointer.c
C
1
#include <stdio.h>
2
3
int main(void) {
4
int *p = NULL;
5
if (p != NULL) printf("%d\n", *p);
6
else printf("p is NULL\n");
7
8
/* Function returning NULL on failure */
9
int nums[] = {10, 20, 30};
10
int *found = NULL;
11
for (int i = 0; i < 3; i++)
12
if (nums[i] == 20) { found = &nums[i]; break; }
13
if (found) printf("Found: %d\n", *found);
14
return 0;
15
}
Pointer State
Value
Safe to Dereference?
Valid variable
Valid address
Yes
NULL pointer
0x0
No — segfault
Wild pointer
Random/garbage
No — undefined behavior
Dangling pointer
Freed/invalid address
No — use-after-free
Pointer Types
Pointers can point to any data type. void* is a generic pointer that can hold any address but cannot be dereferenced directly — you must cast it first.
int (*ptr_to_arr)[5] = &arr; /* pointer to array of 5 ints */
14
int *arr_of_ptrs[5]; /* array of 5 pointers to int */
15
arr_of_ptrs[0] = &arr[0];
16
return 0;
17
}
Passing Arrays to Functions
When you pass an array to a function, it decays to a pointer. The function can modify the original. Always pass the length separately.
passing_arrays.c
C
1
#include <stdio.h>
2
3
void print_array(int *arr, int len) {
4
for (int i = 0; i < len; i++) printf("%d ", arr[i]);
5
printf("\n");
6
}
7
8
void double_elements(int *arr, int len) {
9
for (int i = 0; i < len; i++) arr[i] *= 2;
10
}
11
12
void print_matrix(int matrix[][4], int rows) {
13
for (int r = 0; r < rows; r++) {
14
for (int c = 0; c < 4; c++) printf("%3d ", matrix[r][c]);
15
printf("\n");
16
}
17
}
18
19
int main(void) {
20
int nums[] = {1, 2, 3, 4, 5};
21
print_array(nums, 5);
22
double_elements(nums, 5);
23
print_array(nums, 5);
24
return 0;
25
}
Pointers and Strings
A string is a char array terminated by '\0'. A char* points to the first character. String manipulation is pointer manipulation.
strings_pointers.c
C
1
#include <stdio.h>
2
#include <string.h>
3
4
int main(void) {
5
const char *msg = "Hello, World!";
6
printf("First char: %c\n", *msg);
7
8
for (const char *p = msg; *p != '\0'; p++)
9
printf("%c ", *p);
10
printf("\n");
11
12
char buf[] = "Hello";
13
buf[0] = 'J'; /* Legal: buf is an array */
14
printf("buf: %s\n", buf);
15
return 0;
16
}
Pointers and Functions
Pointers enable pass-by-reference semantics. A function receiving an address can modify the caller's data.
pointers_functions.c
C
1
#include <stdio.h>
2
3
void swap(int *a, int *b) {
4
int temp = *a; *a = *b; *b = temp;
5
}
6
7
void get_min_max(int *arr, int len, int *min, int *max) {
8
*min = arr[0]; *max = arr[0];
9
for (int i = 1; i < len; i++) {
10
if (arr[i] < *min) *min = arr[i];
11
if (arr[i] > *max) *max = arr[i];
12
}
13
}
14
15
int main(void) {
16
int x = 10, y = 20;
17
swap(&x, &y);
18
printf("swap: x=%d, y=%d\n", x, y);
19
20
int nums[] = {5, 3, 8, 1, 9, 2};
21
int min, max;
22
get_min_max(nums, 6, &min, &max);
23
printf("min=%d, max=%d\n", min, max);
24
return 0;
25
}
⚠
warning
Never return a pointer to a local variable. Local variables are destroyed when the function returns. Use static variables, heap allocation, or pass the result buffer as a parameter.
Dangling and Wild Pointers
A dangling pointer points to freed or out-of-scope memory. A wild pointer is uninitialized. Both are dangerous.
A double pointer stores the address of another pointer. Essential for modifying pointers inside functions and building arrays of strings.
pointer_to_pointer.c
C
1
#include <stdio.h>
2
#include <stdlib.h>
3
4
void allocate_int(int **pp, int value) {
5
*pp = (int *)malloc(sizeof(int));
6
if (*pp) **pp = value;
7
}
8
9
int main(void) {
10
int *p = NULL;
11
allocate_int(&p, 42);
12
if (p) { printf("*p = %d\n", *p); free(p); }
13
14
int x = 10, *ip = &x, **ipp = &ip;
15
printf("**ipp = %d\n", **ipp); /* ip -> x -> 10 */
16
return 0;
17
}
Common Pointer Patterns
patterns.c
C
1
#include <stdio.h>
2
#include <string.h>
3
#include <ctype.h>
4
5
int string_length(const char *s) {
6
const char *p = s;
7
while (*p) p++;
8
return (int)(p - s);
9
}
10
11
void to_upper(char *s) {
12
while (*s) { *s = toupper((unsigned char)*s); s++; }
13
}
14
15
const char *find_char(const char *s, char c) {
16
while (*s) { if (*s == c) return s; s++; }
17
return NULL;
18
}
19
20
void copy_string(char *dst, const char *src) {
21
while ((*dst++ = *src++));
22
}
23
24
int filter_positive(int *src, int *dst, int len) {
25
int count = 0;
26
for (int *end = src + len; src < end; src++)
27
if (*src > 0) { *dst++ = *src; count++; }
28
return count;
29
}
30
31
int main(void) {
32
printf("Length: %d\n", string_length("Hello"));
33
char buf[] = "hello world";
34
to_upper(buf);
35
printf("Upper: %s\n", buf);
36
37
const char *f = find_char("Hello", 'l');
38
if (f) printf("Found at offset: %td\n", f - "Hello");
39
40
int nums[] = {-3, 5, -1, 8, 0, -2, 7};
41
int filtered[7];
42
int count = filter_positive(nums, filtered, 7);
43
for (int i = 0; i < count; i++) printf("%d ", filtered[i]);
44
printf("\n");
45
return 0;
46
}
Pointer Pitfalls
pitfalls.c
C
1
#include <stdio.h>
2
#include <stdlib.h>
3
#include <string.h>
4
5
void overflow_example(void) {
6
char buf[5];
7
strncpy(buf, "Hello World!", sizeof(buf));
8
buf[sizeof(buf) - 1] = '\0';
9
}
10
11
void use_after_free(void) {
12
int *p = malloc(sizeof(int)); *p = 42;
13
free(p); p = NULL;
14
}
15
16
void double_free(void) {
17
int *p = malloc(sizeof(int));
18
free(p); p = NULL;
19
}
20
21
int main(void) { return 0; }
✓
best practice
Rules: (1) Initialize to NULL or valid memory, (2) Check malloc return values, (3) Set to NULL after free, (4) Never use freed memory, (5) Pass array lengths separately.