|$ curl https://forge-ai.dev/api/markdown?path=docs/c/functions
$cat docs/c-—-functions.md
updated Recently·45 min read·published

C — Functions

CFunctionsScopeBeginnerBeginner🎯Free Tools
Introduction

Functions are the fundamental unit of code organization in C. Every C program is a collection of functions, with main() as the entry point. Functions enable code reuse, modularity, abstraction, and testability. Unlike some languages, C functions can only return a single value — to return multiple values, you use pointers or structs.

C passes all arguments by value — the function receives a copy. To modify the caller's variable, you pass a pointer to it. This is the core mechanism for "pass by reference" in C. Understanding this distinction, along with scope rules, storage classes, and recursion, is essential for writing correct C programs.

This page covers function declarations, definitions, parameter passing, return values, scope, storage classes, recursion, function pointers, inline functions, and variadic functions.

intro.c
C
1#include <stdio.h>
2
3// Function declaration (prototype)
4int add(int a, int b);
5void greet(const char *name);
6
7// Function definition
8int add(int a, int b) {
9 return a + b;
10}
11
12void greet(const char *name) {
13 printf("Hello, %s!\n", name);
14}
15
16int main(void) {
17 int sum = add(3, 4);
18 greet("World");
19 printf("Sum: %d\n", sum);
20 return 0;
21}
Declaration vs Definition

A declaration (prototype) tells the compiler the function's signature — return type, name, and parameter types. A definitionprovides the actual body. Declarations allow you to call functions before they're defined in the file, and are essential for multi-file programs.

declaration.c
C
1#include <stdio.h>
2
3// Declarations (prototypes) — no function body
4int multiply(int x, int y);
5double circle_area(double radius);
6void swap(int *a, int *b);
7
8// Definition of multiply
9int multiply(int x, int y) {
10 return x * y;
11}
12
13// Definition of circle_area
14double circle_area(double radius) {
15 return 3.14159265358979 * radius * radius;
16}
17
18// Definition of swap
19void swap(int *a, int *b) {
20 int temp = *a;
21 *a = *b;
22 *b = temp;
23}
24
25int main(void) {
26 printf("3 * 4 = %d\n", multiply(3, 4));
27 printf("Area(r=5) = %.2f\n", circle_area(5.0));
28
29 int x = 10, y = 20;
30 printf("Before swap: x=%d, y=%d\n", x, y);
31 swap(&x, &y);
32 printf("After swap: x=%d, y=%d\n", x, y);
33
34 return 0;
35}

info

Always put function declarations in header files (.h) and definitions in source files (.c). This enables separate compilation and prevents inconsistencies between translation units.
Pass by Value

All arguments in C are passed by value — the function receives a copy of the argument. Modifying the parameter inside the function does not affect the caller's variable. To modify the caller's data, you must pass a pointer.

pass-by-value.c
C
1#include <stdio.h>
2
3// This does NOT modify the caller's variable
4void try_to_double(int x) {
5 x = x * 2; // modifies the local copy only
6 printf("Inside try_to_double: x = %d\n", x);
7}
8
9// This DOES modify the caller's variable (pass by pointer)
10void actually_double(int *x) {
11 *x = *x * 2; // dereferences the pointer to modify original
12 printf("Inside actually_double: *x = %d\n", *x);
13}
14
15// Common mistake: swapping with pass by value (doesn't work)
16void bad_swap(int a, int b) {
17 int temp = a;
18 a = b;
19 b = temp;
20 // a and b are copies — caller is unaffected
21}
22
23// Correct swap: pass pointers
24void good_swap(int *a, int *b) {
25 int temp = *a;
26 *a = *b;
27 *b = temp;
28}
29
30int main(void) {
31 int val = 10;
32
33 try_to_double(val);
34 printf("After try_to_double: val = %d\n", val); // still 10
35
36 actually_double(&val);
37 printf("After actually_double: val = %d\n", val); // 20
38
39 int x = 1, y = 2;
40 bad_swap(x, y);
41 printf("After bad_swap: x=%d, y=%d\n", x, y); // 1, 2
42
43 good_swap(&x, &y);
44 printf("After good_swap: x=%d, y=%d\n", x, y); // 2, 1
45
46 return 0;
47}

warning

Arrays are special — when you pass an array to a function, it decays to a pointer to the first element. This means the function CAN modify the original array elements. Using const on array parameters prevents this.
Return Values & Pointers

A function can return a value via the return statement. You can return any type except arrays and functions. Returning a pointer to a local variable is undefined behavior — the memory is freed when the function exits.

return-values.c
C
1#include <stdio.h>
2#include <stdlib.h>
3#include <string.h>
4
5// Returning a computed value
6int factorial(int n) {
7 int result = 1;
8 for (int i = 2; i <= n; i++) {
9 result *= i;
10 }
11 return result;
12}
13
14// Returning a pointer — must point to heap or static data
15char *create_greeting(const char *name) {
16 size_t len = strlen("Hello, ") + strlen(name) + 2;
17 char *greeting = malloc(len);
18 if (greeting) {
19 snprintf(greeting, len, "Hello, %s!", name);
20 }
21 return greeting; // caller must free()
22}
23
24// WARNING: returning address of local variable
25int *dangerous(void) {
26 int local = 42;
27 return &local; // UNDEFINED BEHAVIOR — local is destroyed
28}
29
30// Safe: static variable persists after function returns
31int counter(void) {
32 static int count = 0; // initialized only once
33 return ++count;
34}
35
36int main(void) {
37 printf("5! = %d\n", factorial(5));
38
39 char *g = create_greeting("World");
40 if (g) {
41 printf("%s\n", g);
42 free(g);
43 }
44
45 printf("Counter: %d\n", counter());
46 printf("Counter: %d\n", counter());
47 printf("Counter: %d\n", counter());
48
49 return 0;
50}
Scope

Scope determines where a variable can be accessed. C has three scope levels: file scope (global), function scope, and block scope (local).

scope.c
C
1#include <stdio.h>
2
3// File scope — visible from declaration to end of file
4int global_var = 100;
5static int file_local = 200; // internal linkage
6
7void function_a(void) {
8 // Function scope — label names
9 // Block scope — local variables
10 int local = 1; // block scope of function_a
11
12 if (local > 0) {
13 int inner = 2; // block scope of if-block
14 printf("global: %d, local: %d, inner: %d\n",
15 global_var, local, inner);
16 }
17 // inner is no longer accessible here
18
19 for (int i = 0; i < 3; i++) {
20 // C99: i is scoped to the for block
21 }
22 // i is no longer accessible here
23}
24
25void function_b(void) {
26 // Different function, different scope — no conflict
27 int local = 99;
28 printf("function_b local: %d\n", local);
29}
30
31int main(void) {
32 // Block scope — only accessible within main
33 int main_only = 42;
34
35 function_a();
36 function_b();
37 printf("global: %d\n", global_var);
38
39 // Shadowing — inner scope hides outer
40 int x = 10;
41 {
42 int x = 20; // shadows outer x
43 printf("inner x: %d\n", x);
44 }
45 printf("outer x: %d\n", x); // 10
46
47 return 0;
48}
Static Functions & extern

The static keyword on a function limits its visibility to the current translation unit (internal linkage). The extern keyword declares a variable or function defined in another file (external linkage).

linkage.c
C
1// math_utils.c
2#include <stdio.h>
3
4// static — only visible within this file
5static int internal_helper(int x) {
6 return x * x + 1;
7}
8
9// public function — visible to other files
10int compute(int x) {
11 return internal_helper(x) + x;
12}
13
14// config.c
15#include <stdio.h>
16
17extern int shared_counter; // defined in main.c
18
19void increment(void) {
20 shared_counter++;
21}
22
23// main.c
24#include <stdio.h>
25
26int shared_counter = 0; // external linkage
27
28// static function — cannot be called from other files
29static void print_status(void) {
30 printf("Counter: %d\n", shared_counter);
31}
32
33int compute(int x); // declared in math_utils.c
34
35int main(void) {
36 printf("compute(5) = %d\n", compute(5));
37 shared_counter = 10;
38 print_status();
39 return 0;
40}
Recursion

A recursive function calls itself. Every recursion needs a base case to terminate and a recursive case that moves toward the base. Recursion uses stack space — deep recursion can cause stack overflow.

recursion.c
C
1#include <stdio.h>
2
3// Factorial: n! = n * (n-1)!, base case: 0! = 1
4long long factorial(int n) {
5 if (n <= 1) return 1; // base case
6 return n * factorial(n - 1); // recursive case
7}
8
9// Fibonacci: fib(n) = fib(n-1) + fib(n-2), base: 0,1
10// WARNING: O(2^n) time — exponential! Use iterative for large n.
11long long fibonacci(int n) {
12 if (n <= 0) return 0;
13 if (n == 1) return 1;
14 return fibonacci(n - 1) + fibonacci(n - 2);
15}
16
17// Tail-recursive factorial — O(1) stack space with optimization
18long long factorial_tail(int n, long long acc) {
19 if (n <= 1) return acc;
20 return factorial_tail(n - 1, n * acc);
21}
22
23// Towers of Hanoi
24void hanoi(int n, char from, char to, char aux) {
25 if (n == 1) {
26 printf("Move disk 1 from %c to %c\n", from, to);
27 return;
28 }
29 hanoi(n - 1, from, aux, to);
30 printf("Move disk %d from %c to %c\n", n, from, to);
31 hanoi(n - 1, aux, to, from);
32}
33
34// Binary search (recursive)
35int bsearch(int arr[], int lo, int hi, int target) {
36 if (lo > hi) return -1;
37 int mid = lo + (hi - lo) / 2;
38 if (arr[mid] == target) return mid;
39 if (arr[mid] < target) return bsearch(arr, mid + 1, hi, target);
40 return bsearch(arr, lo, mid - 1, target);
41}
42
43int main(void) {
44 printf("10! = %lld\n", factorial(10));
45 printf("tail(10) = %lld\n", factorial_tail(10, 1));
46 printf("fib(10) = %lld\n", fibonacci(10));
47
48 printf("\nTowers of Hanoi (3 disks):\n");
49 hanoi(3, 'A', 'C', 'B');
50
51 int arr[] = {1, 3, 5, 7, 9, 11, 13};
52 int idx = bsearch(arr, 0, 6, 7);
53 printf("\nSearch for 7: index %d\n", idx);
54
55 return 0;
56}
ApproachTimeSpaceWhen to Use
Recursive factorialO(n)O(n) stackEducational, small n
Iterative factorialO(n)O(1)Production code
Recursive fibonacciO(2^n)O(n) stackNever (use iterative)
Tail-recursiveO(n)O(1)*With compiler optimization
📝

note

Tail-call optimization (TCO) is optional in C — the compiler may convert tail recursion into a loop. GCC with -O2 performs TCO; Clang does too. However, do not rely on it for correctness — only for performance.
Function Pointers

Function pointers store the address of a function and allow you to call functions indirectly. They enable callbacks, strategy patterns, and dynamic dispatch.

func-pointers.c
C
1#include <stdio.h>
2
3int add(int a, int b) { return a + b; }
4int sub(int a, int b) { return a - b; }
5int mul(int a, int b) { return a * b; }
6
7// Function that takes a function pointer as parameter
8int apply(int a, int b, int (*operation)(int, int)) {
9 return operation(a, b);
10}
11
12// Sorting with function pointer (qsort pattern)
13int compare_ints(const void *a, const void *b) {
14 return (*(int *)a - *(int *)b);
15}
16
17int main(void) {
18 // Declare and assign a function pointer
19 int (*op)(int, int) = add;
20 printf("add: %d\n", op(3, 4));
21
22 // Pass function pointer as argument
23 printf("apply(add): %d\n", apply(10, 5, add));
24 printf("apply(sub): %d\n", apply(10, 5, sub));
25 printf("apply(mul): %d\n", apply(10, 5, mul));
26
27 // Array of function pointers
28 int (*operations[])(int, int) = {add, sub, mul};
29 const char *names[] = {"add", "sub", "mul"};
30
31 for (int i = 0; i < 3; i++) {
32 printf("%s(10,5) = %d\n", names[i], operations[i](10, 5));
33 }
34
35 // qsort — standard library uses function pointers
36 int arr[] = {5, 2, 8, 1, 9, 3};
37 int n = sizeof(arr) / sizeof(arr[0]);
38 qsort(arr, n, sizeof(int), compare_ints);
39 printf("Sorted: ");
40 for (int i = 0; i < n; i++) printf("%d ", arr[i]);
41 printf("\n");
42
43 return 0;
44}
Inline & _Noreturn

The inline keyword (C99) suggests the compiler embed the function body at the call site, avoiding function call overhead. The _Noreturn keyword (C11) tells the compiler the function never returns.

inline.c
C
1#include <stdio.h>
2#include <stdlib.h>
3
4// inline — compiler may inline this function
5static inline int square(int x) {
6 return x * x;
7}
8
9static inline int max(int a, int b) {
10 return (a > b) ? a : b;
11}
12
13// _Noreturn — function never returns (C11)
14_Noreturn void fatal_error(const char *msg) {
15 fprintf(stderr, "FATAL: %s\n", msg);
16 exit(1);
17}
18
19// Common pattern: _Noreturn for assertion failure
20_Noreturn void assert_failed(const char *file, int line) {
21 fprintf(stderr, "Assertion failed at %s:%d\n", file, line);
22 abort();
23}
24
25int main(void) {
26 printf("square(7) = %d\n", square(7));
27 printf("max(3,9) = %d\n", max(3, 9));
28
29 // fatal_error("something went wrong"); // would not return
30
31 return 0;
32}
Variadic Functions

Variadic functions accept a variable number of arguments using stdarg.h. The most famous example is printf. You must have at least one fixed parameter before the variadic part.

variadic.c
C
1#include <stdio.h>
2#include <stdarg.h>
3
4// Sum of variable number of integers
5int sum(int count, ...) {
6 va_list args;
7 va_start(args, count);
8
9 int total = 0;
10 for (int i = 0; i < count; i++) {
11 total += va_arg(args, int);
12 }
13
14 va_end(args);
15 return total;
16}
17
18// Custom printf-like function
19void debug_log(const char *prefix, const char *fmt, ...) {
20 va_list args;
21 va_start(args, fmt);
22
23 printf("[%s] ", prefix);
24 vprintf(fmt, args); // vprintf handles va_list
25 printf("\n");
26
27 va_end(args);
28}
29
30// Maximum of variable number of doubles (last arg is 0.0 sentinel)
31double find_max(double first, ...) {
32 va_list args;
33 va_start(args, first);
34
35 double max_val = first;
36 double val;
37 while ((val = va_arg(args, double)) != 0.0) {
38 if (val > max_val) max_val = val;
39 }
40
41 va_end(args);
42 return max_val;
43}
44
45int main(void) {
46 printf("sum(3, 10,20,30) = %d\n", sum(3, 10, 20, 30));
47 printf("sum(5, 1,2,3,4,5) = %d\n", sum(5, 1, 2, 3, 4, 5));
48
49 debug_log("INFO", "Processing %d items", 42);
50 debug_log("WARN", "Value %s is too low", "high");
51
52 printf("max: %.1f\n", find_max(3.0, 7.0, 2.0, 9.0, 1.0, 0.0));
53
54 return 0;
55}

warning

Variadic functions have no type safety — the compiler cannot check that argument types match what va_arg expects. Always pass the correct count and types. The sentinel value pattern (ending with 0 or NULL) must be documented clearly.
$Blueprint — Engineering Documentation·Section ID: C-FUNCTIONS·Revision: 1.0