Parameters and Return Values

C passes every argument by value: the function receives a copy. That simple rule explains why a function can't change your variables directly, how arrays seem to be an exception, and how C functions "return" several results through pointer parameters.

Arguments are copies

C
#include <stdio.h>

void try_to_double(int n) {
    n = n * 2;
    printf("inside:  %d\n", n);
}

int main(void) {
    int value = 21;
    try_to_double(value);
    printf("outside: %d\n", value);
    return 0;
}
Output
inside:  42
outside: 21

The function doubled its own copy. To change the caller's value, either return the new value, or pass the variable's address so the function can write through it.

Return the result

C
#include <stdio.h>

int doubled(int n) {
    return n * 2;
}

int main(void) {
    int value = 21;
    value = doubled(value);
    printf("%d\n", value);
    return 0;
}
Output
42

When a function produces one value, returning it is the clearest design.

Several results: pointer parameters

A function can only return one value. To produce more, the caller passes the addresses of its variables (with &), and the function stores through them (with *). Pointers get a full lesson later (31); here's the pattern:

C
#include <stdio.h>

void split_time(int total_seconds, int *minutes, int *seconds) {
    *minutes = total_seconds / 60;
    *seconds = total_seconds % 60;
}

int main(void) {
    int m, s;
    split_time(754, &m, &s);
    printf("754 s = %d min %d s\n", m, s);
    return 0;
}
Output
754 s = 12 min 34 s

This is exactly how scanf stores what it reads into your variables.

Returning a status and a result

A very common C convention: return whether the operation succeeded, and deliver the actual result through a pointer:

C
#include <stdio.h>
#include <stdbool.h>

bool safe_divide(int a, int b, int *result) {
    if (b == 0) {
        return false;
    }
    *result = a / b;
    return true;
}

int main(void) {
    int q;
    if (safe_divide(17, 5, &q)) printf("17 / 5 = %d\n", q);
    if (!safe_divide(17, 0, &q)) printf("17 / 0 is not allowed\n");
    return 0;
}
Output
17 / 5 = 3
17 / 0 is not allowed

Arrays as parameters

An array argument isn't copied: the function receives a pointer to its first element (lesson 32 explains why). So the function can change the caller's array, and it can't know the array's length — you pass that separately:

C
#include <stdio.h>

void scale(double values[], size_t count, double factor) {
    for (size_t i = 0; i < count; i++) {
        values[i] *= factor;
    }
}

double sum(const double values[], size_t count) {
    double total = 0;
    for (size_t i = 0; i < count; i++) total += values[i];
    return total;
}

int main(void) {
    double prices[] = { 10.0, 4.5, 20.0 };
    scale(prices, 3, 1.1);
    printf("%.2f %.2f %.2f, sum %.2f\n", prices[0], prices[1], prices[2], sum(prices, 3));
    return 0;
}
Output
11.00 4.95 22.00, sum 37.95

const in sum promises not to modify the array — and the compiler enforces it. Mark array and pointer parameters const whenever the function only reads them (lesson 35).

Argument conversions

With a prototype in scope, arguments are converted to the parameter types, just like assignment. Passing 7 to a double parameter gives 7.0; passing 3.9 to an int parameter truncates to 3 (and -Wconversion would warn).

Structs by value

Structures (lesson 40) are copied too when passed by value. For large structs, passing a pointer — usually a const pointer — avoids copying all of the data (lesson 41).

Try it yourself

Write min_max, which finds the smallest and largest values in an array and delivers both through pointer parameters.

Try it yourself · main.c
Edit the code, copy it into main.c, then compile and run it with cc -std=c17 -Wall main.c -o main && ./main. How to set up a C compiler
Output
min 6, max 21