C Functions and Pointers — Pass by Value, Pointer Parameters, and Function Pointers
In this tutorial, you will learn about C Functions and Pointers. We cover key concepts, practical examples, and best practices to help you master this topic.
C functions use pass-by-value semantics where parameters are copied. Pointers enable functions to modify caller variables, pass large structs efficiently, and implement callback mechanisms through function pointers.
Why It Matters
Understanding how C passes arguments is essential for writing correct programs. Pass-by-value means functions cannot modify their arguments unless you pass pointers. Function pointers enable generic programming and callback patterns used in qsort, signal handlers, and event-driven systems.
Real-World Use
The C standard library's qsort function takes a function pointer comparator. Signal handlers are function pointers. The Linux kernel uses function pointers in device driver operations. GUI libraries use callback function pointers for event handling.
What You Will Learn
- Pass-by-value semantics and why they matter
- Using pointer parameters to modify caller variables
- Passing arrays to functions
- Function pointers and callback patterns
- Returning pointers from functions
Learning Path
flowchart LR A[Pointer Arithmetic] --> B[Functions & Pointers
You are here] B --> C[Dynamic Memory] C --> D[Structs] D --> E[Memory Layout] style B fill:#f90,color:#fff
Pass by Value
In C, function arguments are always passed by value -- the function receives a copy:
#include <stdio.h>
void try_modify(int x) {
x = 100; // Modifies only the local copy
printf("Inside function: x = %d\n", x);
}
int main() {
int value = 10;
printf("Before: value = %d\n", value);
try_modify(value);
printf("After: value = %d\n", value); // Still 10!
return 0;
}
Expected output:
Before: value = 10
Inside function: x = 100
After: value = 10
The function modifies its own copy. The original value in main is unaffected. This is pass-by-value.
Pointer Parameters for Output
To modify a variable in the caller, pass its address:
#include <stdio.h>
void modify_via_pointer(int *x) {
*x = 100; // Modifies the variable at the address
}
int main() {
int value = 10;
printf("Before: value = %d\n", value);
modify_via_pointer(&value);
printf("After: value = %d\n", value); // 100!
return 0;
}
Expected output:
Before: value = 10
After: value = 100
The pointer itself is still passed by value -- but the value is an address that lets the function access the original variable.
Return Values vs Pointer Parameters
#include <stdio.h>
// Return value approach
int add(int a, int b) {
return a + b;
}
// Pointer parameter approach
void add_ptr(int a, int b, int *result) {
*result = a + b;
}
int main() {
int sum;
// Return value
sum = add(5, 3);
printf("Return value: %d\n", sum);
// Pointer parameter
add_ptr(5, 3, &sum);
printf("Pointer param: %d\n", sum);
return 0;
}
Use pointer parameters when:
- You need to return multiple values
- The function modifies an existing variable
- The data is too large to copy efficiently
Passing Arrays to Functions
Arrays decay to pointers when passed to functions:
#include <stdio.h>
// Array notation -- actually a pointer
int sum_array(int arr[], int size) {
int total = 0;
for (int i = 0; i < size; i++) {
total += arr[i];
}
return total;
}
// Equivalent pointer notation
int sum_ptr(int *arr, int size) {
int total = 0;
for (int i = 0; i < size; i++) {
total += *(arr + i);
}
return total;
}
// Modify array through pointer
void double_elements(int *arr, int size) {
for (int i = 0; i < size; i++) {
arr[i] *= 2;
}
}
int main() {
int numbers[] = {1, 2, 3, 4, 5};
int size = sizeof(numbers) / sizeof(numbers[0]);
printf("Sum: %d\n", sum_array(numbers, size));
double_elements(numbers, size);
printf("Doubled: ");
for (int i = 0; i < size; i++) {
printf("%d ", numbers[i]);
}
printf("\n");
return 0;
}
Expected output:
Sum: 15
Doubled: 2 4 6 8 10
Remember: sizeof(arr) inside the function returns the pointer size (8), not the array size. Always pass the size separately.
Function Pointers
Function pointers store the address of a function, allowing you to call it indirectly:
#include <stdio.h>
// Some functions with the same signature
int add(int a, int b) { return a + b; }
int subtract(int a, int b) { return a - b; }
int multiply(int a, int b) { return a * b; }
int main() {
// Declare a function pointer
// Returns int, takes two int parameters
int (*operation)(int, int);
operation = add;
printf("add(10, 5) = %d\n", operation(10, 5));
operation = subtract;
printf("subtract(10, 5) = %d\n", operation(10, 5));
operation = multiply;
printf("multiply(10, 5) = %d\n", operation(10, 5));
return 0;
}
Expected output:
add(10, 5) = 15
subtract(10, 5) = 5
multiply(10, 5) = 50
Function Pointer Syntax
// Declaration: return_type (*name)(parameter_types)
int (*func)(int, int);
// Call (both forms work):
func(10, 5);
(*func)(10, 5);
// Typedef for cleaner syntax:
typedef int (*operation_t)(int, int);
operation_t op = add;
Callback Functions
Function pointers enable callback patterns:
#include <stdio.h>
// Function that takes a callback
void process_array(int *arr, int size, int (*callback)(int)) {
for (int i = 0; i < size; i++) {
arr[i] = callback(arr[i]);
}
}
// Callback functions
int double_it(int x) { return x * 2; }
int square(int x) { return x * x; }
int negate(int x) { return -x; }
int main() {
int numbers[] = {1, 2, 3, 4, 5};
int size = sizeof(numbers) / sizeof(numbers[0]);
process_array(numbers, size, double_it);
printf("Doubled: ");
for (int i = 0; i < size; i++) printf("%d ", numbers[i]);
printf("\n");
process_array(numbers, size, square);
printf("Squared: ");
for (int i = 0; i < size; i++) printf("%d ", numbers[i]);
printf("\n");
return 0;
}
Expected output:
Doubled: 2 4 6 8 10
Squared: 4 16 36 64 100
Returning Pointers from Functions
Functions can return pointers, but careful with lifetime:
#include <stdio.h>
#include <stdlib.h>
// CORRECT: return pointer to static data
int *get_static_value() {
static int value = 42;
return &value;
}
// CORRECT: return pointer to heap-allocated data
int *create_int(int value) {
int *p = malloc(sizeof(int));
if (p) {
*p = value;
}
return p;
}
// WRONG: returning pointer to local variable
int *get_local() {
int x = 10;
return &x; // x is destroyed when function returns!
}
int main() {
int *p1 = get_static_value();
printf("Static: %d\n", *p1);
int *p2 = create_int(99);
printf("Heap: %d\n", *p2);
free(p2);
// int *p3 = get_local(); // Dangling pointer!
return 0;
}
const with Pointer Parameters
Use const to indicate that a function will not modify data through a pointer:
#include <stdio.h>
// Read-only access: function promises not to modify
void print_array(const int *arr, int size) {
for (int i = 0; i < size; i++) {
printf("%d ", arr[i]);
}
printf("\n");
}
// Modifiable access
void scale_array(int *arr, int size, int factor) {
for (int i = 0; i < size; i++) {
arr[i] *= factor;
}
}
int main() {
int nums[] = {10, 20, 30};
int size = sizeof(nums) / sizeof(nums[0]);
print_array(nums, size);
scale_array(nums, size, 2);
print_array(nums, size);
return 0;
}
Common Mistakes
1. Forgetting That Arrays Decay to Pointers
void func(int arr[10]) {
// arr is a pointer, not an array!
// sizeof(arr) == 8, not 40
}
Always pass array size as a separate parameter.
2. Returning Pointers to Local Variables
int *bad() {
int x = 42;
return &x; // x is destroyed when function returns
}
Use static variables, heap allocation, or pass a pointer parameter.
3. Incorrect Function Pointer Syntax
int *func(); // Function returning int*
int (*func)(); // Function pointer returning int
Parentheses around *func make it a function pointer.
4. Not Using const for Input Parameters
void process(int *arr) {
// Caller does not know if arr will be modified
}
Use const int *arr when the function only reads.
5. Confusing Pointer to Array with Array of Pointers
int (*arr)[5]; // Pointer to array of 5 ints
int *arr[5]; // Array of 5 pointers to int
Practice Questions
Why does C use pass-by-value? For simplicity and performance. Pass-by-value prevents accidental modification of caller variables and allows the compiler to optimize more aggressively.
How do you modify a caller's variable from a function? Pass the address using & and accept a pointer parameter. The function dereferences the pointer to modify the original.
What is a function pointer? A variable that stores the address of a function. It allows calling the function indirectly, enabling callbacks and dynamic dispatch.
Why must you pass array size separately to functions? Because arrays decay to pointers in function parameters. sizeof(arr) returns the pointer size, not the array size.
Challenge: Write a function that takes a function pointer and applies it to each element of an array, returning a new array.
Mini Project: Generic Map Function
#include <stdio.h>
#include <stdlib.h>
typedef int (*map_func_t)(int);
int *map(int *arr, int size, map_func_t func) {
int *result = malloc(size * sizeof(int));
if (!result) return NULL;
for (int i = 0; i < size; i++) {
result[i] = func(arr[i]);
}
return result;
}
int triple(int x) { return x * 3; }
int main() {
int nums[] = {1, 2, 3, 4, 5};
int size = sizeof(nums) / sizeof(nums[0]);
int *tripled = map(nums, size, triple);
printf("Original: ");
for (int i = 0; i < size; i++) printf("%d ", nums[i]);
printf("\nTripled: ");
for (int i = 0; i < size; i++) printf("%d ", tripled[i]);
printf("\n");
free(tripled);
return 0;
}
FAQ
What is Next
Now that you understand functions and pointers, proceed to Dynamic Memory to learn about malloc, calloc, realloc, and free.