Skip to content
Laviyx Learn

Try

C course menu

Pointers and Functions in C: Output Parameters, Returning Pointers and Function Pointers

Use pointers with functions in C: output parameters, const-correct pointer parameters, returning pointers safely and the dangling-pointer bug, function pointers, callbacks, and sorting with qsort and a comparison function.

Intermediate9 min readUpdated
On this page

Pointers and functions work together in three ways: functions can take pointers, return pointers, and be pointed to.

Output parameters#

A function can only return one value, but it can write results through pointer parameters. Library functions do this all the time, for example scanf and strtol:

C
#include <stdio.h>

int parse_time(const char *text, int *hours, int *minutes) {
    if (sscanf(text, "%d:%d", hours, minutes) != 2) return 0;
    return *hours >= 0 && *hours < 24 && *minutes >= 0 && *minutes < 60;
}

int main(void) {
    int h, m;
    if (parse_time("09:45", &h, &m)) printf("%d minutes after midnight\n", h * 60 + m);
    if (!parse_time("25:00", &h, &m)) printf("invalid time\n");
    return 0;
}
Output
585 minutes after midnight
invalid time

The return value reports success; the pointers carry the data. This is a very common C pattern.

const and pointers#

Where const goes changes what is protected:

DeclarationCan change *p?Can change p?
const int *pNoYes
int *const pYesNo
const int *const pNoNo

Read from right to left: const int *p is "p is a pointer to an int that is const". Use const on parameters the function only reads.

Returning pointers#

A function may return a pointer to data that outlives the call: an element of an array the caller passed in, a static variable, or memory from malloc. It must never return the address of its own local variable, which disappears when the function returns:

C
int *broken(void) {
    int local = 42;
    return &local;
}

This compiles, but GCC warns function returns address of local variable. The caller receives a dangling pointer: an address whose variable no longer exists. Treat this warning as an error.

Returning a pointer into the caller's data is fine:

C
#include <stdio.h>

int *find_max(int *a, int n) {
    int *best = a;
    for (int *p = a + 1; p < a + n; p++) {
        if (*p > *best) best = p;
    }
    return best;
}

int main(void) {
    int scores[] = {55, 91, 73, 88};
    int *top = find_max(scores, 4);
    *top = 100;                       /* change it in place */
    printf("%d %d %d %d\n", scores[0], scores[1], scores[2], scores[3]);
    return 0;
}
Output
55 100 73 88

Function pointers#

A function also has an address, and a function pointer stores it. The syntax puts the name in parentheses with a *:

C
#include <stdio.h>

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

int apply(int (*op)(int, int), int x, int y) {
    return op(x, y);
}

int main(void) {
    int (*f)(int, int) = add;
    printf("%d\n", f(3, 4));
    f = multiply;
    printf("%d\n", f(3, 4));
    printf("%d\n", apply(add, 10, 5));
    return 0;
}
Output
7
12
15

Passing a function to another function is called a callback. A typedef makes the type easier to read: typedef int (*BinaryOp)(int, int);.

An array of function pointers works like a simple dispatch table:

C
#include <stdio.h>

typedef double (*Op)(double, double);

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

int main(void) {
    Op ops[] = {add, sub, mul};
    const char *names[] = {"+", "-", "*"};
    for (int i = 0; i < 3; i++) {
        printf("8 %s 2 = %.0f\n", names[i], ops[i](8, 2));
    }
    return 0;
}
Output
8 + 2 = 10
8 - 2 = 6
8 * 2 = 16

Sorting with qsort#

The standard qsort (from <stdlib.h>) sorts any array. You give it a callback that compares two elements, passed as const void *, returning a negative number, zero or a positive number:

C
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int by_value(const void *a, const void *b) {
    int x = *(const int *)a;
    int y = *(const int *)b;
    return (x > y) - (x < y);          /* avoids overflow from x - y */
}

int by_name(const void *a, const void *b) {
    const char *x = *(const char *const *)a;
    const char *y = *(const char *const *)b;
    return strcmp(x, y);
}

int main(void) {
    int nums[] = {42, -7, 19, 3, 88};
    qsort(nums, 5, sizeof nums[0], by_value);
    for (int i = 0; i < 5; i++) printf("%d ", nums[i]);
    printf("\n");

    const char *langs[] = {"python", "c", "java", "cpp"};
    qsort(langs, 4, sizeof langs[0], by_name);
    for (int i = 0; i < 4; i++) printf("%s ", langs[i]);
    printf("\n");
    return 0;
}
Output
-7 3 19 42 88 
c cpp java python 

void * is a generic pointer that can hold any object address; you convert it back to the real type before using it.

Practice#

Practice exercise

Write void for_each(int *a, int n, void (*fn)(int *)) that calls fn on every element, and two callbacks: square (squares the value) and print (prints it followed by a space). Use them on {1, 2, 3, 4}.

Show solution
C
#include <stdio.h>

void for_each(int *a, int n, void (*fn)(int *)) {
    for (int i = 0; i < n; i++) fn(&a[i]);
}

void square(int *x) { *x = *x * *x; }
void print(int *x) { printf("%d ", *x); }

int main(void) {
    int nums[] = {1, 2, 3, 4};
    for_each(nums, 4, square);
    for_each(nums, 4, print);
    printf("\n");
    return 0;
}
Output
1 4 9 16 

Key takeaway#

Finished this lesson?