C Programming Β· Pointers & Functions Β· 10 Programs
Pointers & Functions
The single most important topic in C. Ten progressively harder programs β from passing a variable by address to arrays of function pointers and command-line arguments. Master these patterns and everything else in C opens up.
fun(int *p)
Function receives an address β can modify the original variable
int *fun()
Function returns an address β caller gets a pointer back
int (*p)()
p stores the address of a function β a function pointer
(*p)()
Calls the function through the pointer β indirect call
Pass by Address
β
Swap
β
Pass Array
β
Return Pointer
β
Static Return
β
Fn Pointer
β
Multi Fn Ptr
β
Array Fn Ptr
β
Ptr to Array
β
argv
Program 1 π¬ Passing a Variable by Address β void increment(int *p)
P1
void increment(int *p) β Modify the original variable from inside a function
Pass the address with &x β function receives a pointer β dereferences with *p to change x
Pass by Address
By default C passes everything by value β the function gets a copy. Changes inside the function vanish when it returns. To let a function modify the original, pass the address using &x. The function receives int *p β a pointer to x. It uses (*p)++ to dereference the pointer and increment whatever it points at β which is x itself.
Memory β p holds the address of x, (*p)++ changes x directly
1
increment(&x) β the & operator gives the address of x. That address is passed to the function.
2
int *p β parameter p is a pointer. It receives and stores the address of x.
3
(*p)++ β dereference p to get x, then increment it. Parentheses are needed: *p++ would move the pointer, not increment the value.
4
Back in main β x is now 11 because the function wrote through the pointer to the original memory location.
p1_pass_by_address.c
C
#include <stdio.h>voidincrement(int *p) /* p receives the address of x */
{
(*p)++; /* dereference β reach x β add 1 */
}
intmain()
{
int x = 10;
increment(&x); /* pass address of x */printf("%d\n", x); /* x is now 11 */return0;
}
output
11
Why (*p)++ and not *p++? Postfix ++ has higher precedence than *. So *p++ increments the pointer (moves it forward) and then dereferences β not what you want. The parentheses in (*p)++ force the dereference first, then increment the value.
Program 2 π Swapping Two Numbers β void swap(int *a, int *b)
P2
void swap(int *a, int *b) β Classic three-step pointer swap
Swap is the classic demonstration of pass-by-address. The function receives two addresses. Using a temporary variable it exchanges the values at those addresses β not copies of the values. After the function returns, x and y in main are truly exchanged.
p2_swap.c
C
#include <stdio.h>voidswap(int *a, int *b)
{
int temp = *a; /* save value at a */
*a = *b; /* write b's value into a */
*b = temp; /* write saved into b */
}
intmain()
{
int x = 10, y = 20;
swap(&x, &y);
printf("%d %d\n", x, y); /* 20 10 */return0;
}
output
20 10
Three steps β why temp is needed: If you write *a = *b first, the original value of *a is overwritten and lost. temp saves it before the overwrite happens. This three-step pattern β save, overwrite, restore β is the universal swap for any type.
Program 3 π¦ Passing an Array to a Function β void display(int *p, int n)
P3
void display(int *p, int n) β Array decays to pointer β walk with *(p+i)
Passing arr to a function is the same as passing &arr[0] β pointer arithmetic accesses each element
Array β Pointer
When you write display(arr, 4) the array name arr decays to a pointer to its first element. The function receives int *p β exactly that pointer. Inside the function, *(p+i) computes the address of element i and dereferences it β identical to arr[i] but written in pointer notation.
Pointer arithmetic β *(p+i) reaches each element
p3_pass_array.c
C
#include <stdio.h>voiddisplay(int *p, int n)
{
for (int i = 0; i < n; i++)
printf("%d ", *(p + i)); /* pointer arithmetic */printf("\n");
}
intmain()
{
int arr[] = {10, 20, 30, 40};
display(arr, 4); /* arr decays to &arr[0] */return0;
}
output
10 20 30 40
Three equivalent ways to access element i:*(p+i), p[i], arr[i] β all compile to the same machine instruction. The C standard defines p[i] as exactly *(p+i).
Program 4 π Function Returning a Pointer β int *largest(int *a, int n)
P4
int *largest(int *a, int n) β Walk array with pointer, return address of max element
max starts at &a[0] β if a[i] > *max, update max to &a[i] β return max at end
Return int*
Instead of returning the value of the largest element, this function returns its address. int *max = &a[0] starts the max pointer at the first element. As the loop finds a larger element it updates max to point at that element. At the end, return max gives back the address β caller dereferences with *largest(...) to get the value.
p4_return_pointer.c
C
#include <stdio.h>int *largest(int *a, int n)
{
int *max = &a[0]; /* start: max points at a[0] */for (int i = 1; i < n; i++)
{
if (a[i] > *max)
max = &a[i]; /* update: point at new maximum */
}
return max; /* return ADDRESS of max element */
}
intmain()
{
int arr[] = {15, 80, 20, 45};
printf("Largest: %d\n", *largest(arr, 4)); /* dereference result */return0;
}
output
Largest: 80
This is safe because arr lives in main's stack frame and is still alive when the returned pointer is used. Never return a pointer to a local variable inside the function β that memory is gone when the function returns.
Program 5 ποΈ Returning Static Variables β int *getNumber(void)
P5
int *getNumber(void) β static variables live beyond the function β safe to return their address
static int x survives every function return β its address is always valid
Static Memory
A static local variable is stored in the data segment β not on the stack. It survives for the entire lifetime of the program, initialised only once. This makes its address safe to return from a function. Without static, returning &x where x is a local variable gives a dangling pointer β the memory is reclaimed the moment the function returns.
p5_static_return.c
C
#include <stdio.h>int *getNumber(void)
{
staticint x = 100; /* lives in data segment β always valid */return &x;
}
intmain()
{
int *p = getNumber();
printf("Value : %d\n", *p); /* 100 */
*p = 200; /* can modify through pointer */printf("After : %d\n", *getNumber()); /* 200 */return0;
}
output
Value : 100
After : 200
Static = shared state. Every call to getNumber returns the address of the same variable. Modifying through one pointer changes what all other callers see. This is useful for counters and caches β but can cause subtle bugs if you forget the sharing.
Program 6 π― Pointer to a Function β int (*p)(int, int)
P6
int (*p)(int, int) β p stores a function's address β call any matching function through it
p = add stores address β p(10, 20) calls add indirectly β no * needed to call
Function Pointer
A function pointer stores the address of a function in code memory. The declaration int (*p)(int, int) says: p is a pointer to a function that takes two ints and returns an int. Assign with p = add (no parentheses β the function name alone is its address). Call with p(10, 20) β identical to calling add(10, 20) directly.
Code memory β p holds the address of add()
p6_fn_pointer.c
C
#include <stdio.h>intadd(int a, int b)
{
return a + b;
}
intmain()
{
int (*p)(int, int); /* pointer to fn(int,int)βint */
p = add; /* store address of add */printf("%d\n", p(10, 20)); /* call through pointer β 30 */return0;
}
output
30
Reading the declaration: Start at p. Parens force pointer-ness first: (*p) β p is a pointer. Outside the parens: (int, int) β to a function taking two ints. Far left: int β that returns int. So: p is a pointer to a function(int,int)βint.
Program 7 π Multiple Function Pointers β Swap Functions at Runtime
P7
Same pointer p β different function β same call syntax β runtime dispatch
p = add then p = sub β one pointer variable calls two different functions
Runtime Dispatch
The real power of function pointers: the same pointer variable can call different functions at runtime depending on what it is assigned to. This is the foundation of callbacks, plug-in systems, and polymorphism in C. The call syntax p(a, b) never changes β only the assignment changes.
p7_multi_fn_ptr.c
C
#include <stdio.h>intadd(int a, int b) { return a + b; }
intsub(int a, int b) { return a - b; }
intmain()
{
int (*p)(int, int);
p = add;
printf("add(20,10) = %d\n", p(20, 10)); /* 30 */
p = sub; /* re-assign */printf("sub(20,10) = %d\n", p(20, 10)); /* 10 */return0;
}
output
add(20,10) = 30
sub(20,10) = 10
Program 8 ποΈ Array of Function Pointers β int (*fun[2])(int, int)
P8
int (*fun[2])(int, int) β Two slots, two functions β call by index
fun[0] = add, fun[1] = sub β call with fun[0](10,20) β dispatch table pattern
Dispatch Table
An array of function pointers is a dispatch table β a lookup structure that maps an index to a function. Instead of a long if-else or switch, you load the right function into an array slot and call by index. This pattern powers menu-driven programs, calculators, and state machines.
Memory layout β fun[] is an array where each slot holds a function address
p8_array_fn_ptr.c
C
#include <stdio.h>intadd(int a, int b) { return a + b; }
intsub(int a, int b) { return a - b; }
intmul(int a, int b) { return a * b; }
intmain()
{
/* int (*fun[5])(int,int) β array of fn pointers */int (*fun[3])(int, int);
fun[0] = add;
fun[1] = sub;
fun[2] = mul;
char *names[] = {"add", "sub", "mul"};
for (int i = 0; i < 3; i++)
printf("%s(20,10) = %d\n", names[i], fun[i](20, 10));
return0;
}
output
add(20,10) = 30
sub(20,10) = 10
mul(20,10) = 200
Declaration reading:int (*fun[3])(int,int) β start at fun, go right β [3] = array of 3, go left inside parens β * = of pointers, exit parens, go right β (int,int) = to functions taking two ints, go left β int = returning int. fun is a 3-element array of pointers to int(int,int) functions.
Program 9 π Pointer to an Array β void display(int (*p)[5])
P9
int (*p)[5] β p points to the entire 5-element array β pass &arr not arr
(*p)[i] reads each element β p++ skips an entire row β used for 2D arrays
Ptr to Array
int (*p)[5] is not an array of pointers β it is a pointer to a 5-element int array. Pass &arr (address of the whole array) not just arr. Inside the function, (*p)[i] first dereferences p to get the array, then subscripts it. When p is incremented, it jumps by the size of an entire row β the foundation of 2D array traversal.
p9_ptr_to_array.c
C
#include <stdio.h>voiddisplay(int (*p)[5]) /* pointer to 5-element int array */
{
for (int i = 0; i < 5; i++)
printf("%d ", (*p)[i]); /* deref p β array β subscript */printf("\n");
}
/* 2D version β p++ skips one full row */voiddisplay2D(int (*p)[5], int rows)
{
for (int r = 0; r < rows; r++, p++)
{
for (int c = 0; c < 5; c++)
printf("%3d", (*p)[c]);
printf("\n");
}
}
intmain()
{
int arr[5] = {10, 20, 30, 40, 50};
display(&arr); /* pass &arr β address of whole array */int grid[2][5] = {{1,2,3,4,5},{6,7,8,9,10}};
display2D(grid, 2);
return0;
}
output
10 20 30 40 50
1 2 3 4 5
6 7 8 9 10
Key contrast β int *p vs int (*p)[5]:int *p = arr points to the first element. p++ moves by 4 bytes (one int). int (*p)[5] = &arr points to the first row. p++ moves by 20 bytes (five ints) β a full row. This is why 2D array functions use int (*p)[cols].
Program 10 β¨οΈ Command-Line Arguments β int main(int argc, char *argv[])
P10
int main(int argc, char *argv[]) β argv is an array of char pointers β each is a string
argc = count of arguments β argv[0] = program name β argv[1..] = user arguments
argv / argc
char *argv[] is an array of pointers to characters β each pointer points to one command-line argument string. argc is the count. argv[0] is always the program name. The declaration int main(int argc, char *argv[]) is exactly int p(char *a[]) from the declarations lesson β a function taking an array of char pointers.
argv layout β array of char pointers, each pointing to a string
p10_argv.c
C
#include <stdio.h>intmain(int argc, char *argv[])
{
printf("Argument count: %d\n", argc);
printf("Arguments:\n");
for (int i = 0; i < argc; i++)
printf(" argv[%d] = %s\n", i, argv[i]);
return0;
}
run: ./program Hello World
Argument count: 3
Arguments:
argv[0] = program
argv[1] = Hello
argv[2] = World
argv[argc] is always NULL β a sentinel marking the end of the argument list. You can also iterate using a pointer: char **p = argv; while(*p) printf("%s\n", *p++); β the NULL at the end stops the loop automatically.
complete declarations reference
π
Most Important Declarations to Remember
Every declaration pattern from this lesson in one reference table
Reference
Declaration
Meaning
Used in
int *p
Pointer to an integer β stores an address
P1, P2, P4
void fun(int *p)
Function receives an address β modifies original
P1, P2
int *fun(void)
Function returns an address β caller gets pointer
P4, P5
int (*p)(void)
p is a pointer to a function taking no args, returning int
P6
int (*p)(int,int)
Pointer to function taking two ints, returning int
P6, P7
int (*p)[10]
Pointer to a 10-element int array β whole row pointer
P9
int *p[10]
Array of 10 int pointers β each slot holds an address
β
int **p
Pointer to a pointer β double indirection
Advanced
int (*fun[5])(int,int)
Array of 5 function pointers β dispatch table
P8
int main(int argc, char *argv[])
Command-line: argc = count, argv = array of strings
P10
checklist β tick each pattern when confident
P1 β Pass by address:fun(&x) passes the address. int *p receives it. (*p)++ increments the original. Parentheses around *p are required β *p++ moves the pointer instead.
P2 β Swap: Three steps β temp=*a, *a=*b, *b=temp. Must use temp because the first write overwrites the original value.
P3 β Array to function:display(arr, n) β arr decays to &arr[0]. Inside: *(p+i) = p[i] = arr[i]. All three are identical.
P4 β Return pointer:int *fun() β function returns address. Safe if pointing into caller's array or heap. Never return address of a local variable.
P5 β Static return:static int x lives in data segment β survives function return. Address is always valid. All callers share the same variable.
P6 β Function pointer:int (*p)(int,int) β p is a pointer to a function. p = add stores address (no parens). p(10,20) calls through it.
P7 β Multiple fn ptrs: Same pointer p re-assigned to different functions at runtime. The call p(a,b) never changes β only the assignment changes which function runs.
P8 β Array of fn ptrs:int (*fun[3])(int,int) β 3-element dispatch table. fun[0]=add. fun[0](10,20) calls add. Use instead of long if-else chains.
P9 β Pointer to array:int (*p)[5] β whole-row pointer. Pass &arr (not arr). Access with (*p)[i]. p++ skips a full row β essential for 2D arrays.
P10 β argv:char *argv[] = array of char pointers. argv[0] = program name. argc = count. argv[argc] = NULL sentinel.