πŸ“Œ Pointers & Functions β€” Complete Mastery Guide
0%
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
x (in main) 10 addr: 1000 increment(&x) passes 1000 p (in increment) = 1000 int *p β€” holds address (*p)++ modifies x β†’ becomes 11
p1_pass_by_address.c
C
#include <stdio.h>

void increment(int *p)  /* p receives the address of x   */
{
    (*p)++;              /* dereference β†’ reach x β†’ add 1 */
}

int main()
{
    int x = 10;

    increment(&x);       /* pass address of x             */

    printf("%d\n", x);   /* x is now 11                   */

    return 0;
}
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
temp saves *a β€” *a gets *b β€” *b gets temp β€” both originals changed
Two Pointers
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>

void swap(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      */
}

int main()
{
    int x = 10, y = 20;

    swap(&x, &y);

    printf("%d %d\n", x, y);   /* 20 10  */

    return 0;
}
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
10 20 30 40 p+0 β†’ *(p+0)=10 p+1 β†’ *(p+1)=20 p+2 β†’ *(p+2)=30 p+3 β†’ *(p+3)=40 p (= arr) *(p+i) ≑ arr[i] ≑ p[i]
p3_pass_array.c
C
#include <stdio.h>

void display(int *p, int n)
{
    for (int i = 0; i < n; i++)
        printf("%d ", *(p + i));  /* pointer arithmetic */
    printf("\n");
}

int main()
{
    int arr[] = {10, 20, 30, 40};

    display(arr, 4);   /* arr decays to &arr[0] */

    return 0;
}
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 */
}

int main()
{
    int arr[] = {15, 80, 20, 45};

    printf("Largest: %d\n", *largest(arr, 4)); /* dereference result */

    return 0;
}
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)
{
    static int x = 100;  /* lives in data segment β€” always valid */
    return &x;
}

int main()
{
    int *p = getNumber();

    printf("Value : %d\n",  *p);     /* 100       */
    *p = 200;                       /* can modify through pointer */
    printf("After : %d\n",  *getNumber()); /* 200  */

    return 0;
}
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()
int (*p)(int,int) = addr of add p = add add() {return a+b;} lives in code/text segment p(10,20) β†’ calls add β†’ returns 30
p6_fn_pointer.c
C
#include <stdio.h>

int add(int a, int b)
{
    return a + b;
}

int main()
{
    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  */

    return 0;
}
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>

int add(int a, int b) { return a + b; }
int sub(int a, int b) { return a - b; }

int main()
{
    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 */

    return 0;
}
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
fun[0] = addr of add fun[1] = addr of sub add() return a+b sub() return a-b fun[0](10,20) β†’ 30 fun[1](20,10) β†’ 10
p8_array_fn_ptr.c
C
#include <stdio.h>

int add(int a, int b) { return a + b; }
int sub(int a, int b) { return a - b; }
int mul(int a, int b) { return a * b; }

int main()
{
    /* 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));

    return 0;
}
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>

void display(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 */
void display2D(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");
    }
}

int main()
{
    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);

    return 0;
}
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
argv[0] char* argv[1] char* argv[2] char* "program" "Hello" "World" argc = 3 count of arguments argv[argc] = NULL
p10_argv.c
C
#include <stdio.h>

int main(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]);

    return 0;
}
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
DeclarationMeaningUsed in
int *pPointer to an integer β€” stores an addressP1, P2, P4
void fun(int *p)Function receives an address β€” modifies originalP1, P2
int *fun(void)Function returns an address β€” caller gets pointerP4, P5
int (*p)(void)p is a pointer to a function taking no args, returning intP6
int (*p)(int,int)Pointer to function taking two ints, returning intP6, P7
int (*p)[10]Pointer to a 10-element int array β€” whole row pointerP9
int *p[10]Array of 10 int pointers β€” each slot holds an addressβ€”
int **pPointer to a pointer β€” double indirectionAdvanced
int (*fun[5])(int,int)Array of 5 function pointers β€” dispatch tableP8
int main(int argc, char *argv[])Command-line: argc = count, argv = array of stringsP10
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.