C Function Parameters and Return Values: Pass by Value Guide 2026
Table of Contents
Table of Contents
Understanding Parameters and Arguments
In the previous lesson on C functions, you learned how to declare and define functions. Now let’s go deeper into how data flows in and out of functions through parameters and return values.
Parameters are the variables listed in a function’s declaration — they’re placeholders. Arguments are the actual values you pass when calling the function. Think of parameters as parking spots and arguments as the cars that fill them:
// 'a' and 'b' are PARAMETERS (placeholders)
int add(int a, int b) {
return a + b;
}
int main(void) {
// 10 and 20 are ARGUMENTS (actual values)
int result = add(10, 20);
return 0;
}
The terms are often used interchangeably in casual conversation, but the distinction matters for understanding how C handles data.
Pass by Value in C
C uses pass by value exclusively. When you pass an argument to a function, C copies the value into the parameter. The function works on its own copy. Changes inside the function never affect the caller’s variable:
#include <stdio.h>
void double_it(int x) {
x = x * 2;
printf("Inside function: x = %d\n", x); // 20
}
int main(void) {
int num = 10;
double_it(num);
printf("After function: num = %d\n", num); // Still 10!
return 0;
}
This is fundamentally different from languages like Python or JavaScript where objects are passed by reference. In C, if you want a function to modify the caller’s variable, you must pass a pointer — but that’s a topic for the pointers lesson later in the C roadmap.
Pass by value has advantages: functions can’t accidentally corrupt your data, and reasoning about program state is simpler. You always know that after a function call, your local variables are unchanged (unless you explicitly used pointers).
Multiple Parameters
Functions can accept any number of parameters. Each needs its own type declaration — you can’t shortcut like variable declarations:
// WRONG — won't compile
int add(int a, b);
// CORRECT — each parameter needs its type
int add(int a, int b);
// Multiple types
void print_info(char name[], int age, double gpa) {
printf("Name: %s, Age: %d, GPA: %.2f\n", name, age, gpa);
}
Arguments are matched to parameters by position, not by name. The first argument goes to the first parameter, second to second, and so on:
void show(int x, double y, char c) {
printf("%d %.2f %c\n", x, y, c);
}
// Order matters!
show(42, 3.14, 'A'); // x=42, y=3.14, c='A'
show('A', 42, 3.14); // x=65(ASCII), y=42.0, c=?? — wrong!
Passing Arrays to Functions
Arrays in C behave differently when passed to functions. You can’t pass an entire array by value — instead, C passes a pointer to the first element. This means functions can modify the original array:
#include <stdio.h>
void double_array(int arr[], int size) {
for (int i = 0; i < size; i++) {
arr[i] *= 2; // Modifies the ORIGINAL array!
}
}
int main(void) {
int nums[] = {1, 2, 3, 4, 5};
int size = sizeof(nums) / sizeof(nums[0]);
double_array(nums, size);
// Array is modified!
for (int i = 0; i < size; i++) {
printf("%d ", nums[i]); // 2 4 6 8 10
}
printf("\n");
return 0;
}
Because arrays decay to pointers, you must pass the size separately. There’s no way for a function to determine an array’s length from just the pointer. These three declarations are equivalent inside a function signature:
void process(int arr[], int size); // Most common
void process(int *arr, int size); // Equivalent — pointer notation
void process(int arr[10], int size); // 10 is ignored by compiler
For 2D arrays, you must specify the column count:
void print_matrix(int rows, int cols, int matrix[][3]) {
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
printf("%d ", matrix[i][j]);
}
printf("\n");
}
}
Return Values in Depth
The return statement does two things: it provides a value to the caller and immediately exits the function. The returned value must be compatible with the function’s declared return type:
// Return type is int
int absolute(int n) {
if (n < 0) return -n;
return n;
}
// Return type is double
double average(int arr[], int size) {
int sum = 0;
for (int i = 0; i < size; i++) {
sum += arr[i];
}
return (double)sum / size; // Cast to double for accurate division
}
// Return type is char
char first_letter(char str[]) {
return str[0];
}
Type conversions happen automatically if the types are compatible but different. An int returned from a double function gets promoted; a double returned from an int function gets truncated. Be explicit with casts to avoid surprises, as covered in variables and data types.
Returning Multiple Values
C functions can only return a single value. To return multiple values, you have three options:
Option 1: Use output parameters (pointers):
void min_max(int arr[], int size, int *min, int *max) {
*min = arr[0];
*max = arr[0];
for (int i = 1; i < size; i++) {
if (arr[i] < *min) *min = arr[i];
if (arr[i] > *max) *max = arr[i];
}
}
// Usage
int lo, hi;
int data[] = {3, 1, 4, 1, 5, 9};
min_max(data, 6, &lo, &hi);
printf("Min: %d, Max: %d\n", lo, hi);
Option 2: Return a struct:
typedef struct {
int min;
int max;
} MinMax;
MinMax find_min_max(int arr[], int size) {
MinMax result = {arr[0], arr[0]};
for (int i = 1; i < size; i++) {
if (arr[i] < result.min) result.min = arr[i];
if (arr[i] > result.max) result.max = arr[i];
}
return result;
}
Option 3: Modify an array parameter:
// Write results into a pre-allocated array
void get_stats(int data[], int size, double results[]) {
// results[0] = mean, results[1] = min, results[2] = max
double sum = 0;
results[1] = results[2] = data[0];
for (int i = 0; i < size; i++) {
sum += data[i];
if (data[i] < results[1]) results[1] = data[i];
if (data[i] > results[2]) results[2] = data[i];
}
results[0] = sum / size;
}
Const Parameters
When a function shouldn’t modify an array or pointer parameter, mark it const. This tells both the compiler and the reader that the data is read-only:
// This function promises not to modify the array
int sum_array(const int arr[], int size) {
int total = 0;
for (int i = 0; i < size; i++) {
total += arr[i];
// arr[i] = 0; // COMPILE ERROR — arr is const
}
return total;
}
// Const pointer to string
int string_length(const char str[]) {
int len = 0;
while (str[len] != '\0') len++;
return len;
}
Using const is a best practice that prevents bugs and documents your function’s contract.
Default Argument Promotion
When calling a function without a prototype, or when using variadic functions like printf(), C applies default argument promotion: char and short are promoted to int, and float is promoted to double. This is why printf() uses %d for both char and int, and %f for both float and double.
With proper prototypes, no promotion occurs — the argument is converted to the exact parameter type.
Practical Examples
Here’s a practical program that demonstrates different parameter and return value patterns — a student grade processor:
#include <stdio.h>
// Prototypes
double calculate_average(const int scores[], int count);
char determine_grade(double average);
void print_report(const char name[], const int scores[], int count);
int main(void) {
const char *student = "Alice";
int scores[] = {85, 92, 78, 95, 88};
int count = sizeof(scores) / sizeof(scores[0]);
print_report(student, scores, count);
return 0;
}
double calculate_average(const int scores[], int count) {
int sum = 0;
for (int i = 0; i < count; i++) {
sum += scores[i];
}
return (double)sum / count;
}
char determine_grade(double average) {
if (average >= 90.0) return 'A';
if (average >= 80.0) return 'B';
if (average >= 70.0) return 'C';
if (average >= 60.0) return 'D';
return 'F';
}
void print_report(const char name[], const int scores[], int count) {
printf("Student: %s\n", name);
printf("Scores: ");
for (int i = 0; i < count; i++) {
printf("%d ", scores[i]);
}
double avg = calculate_average(scores, count);
printf("\nAverage: %.1f\n", avg);
printf("Grade: %c\n", determine_grade(avg));
}
Common Mistakes
Assuming arrays know their size: Unlike strings (which end with \0), integer arrays have no built-in length. Always pass size as a separate parameter.
Returning a local array: Never return a pointer to a local array — it’s destroyed when the function exits:
// WRONG — undefined behavior!
int* bad_function(void) {
int arr[5] = {1, 2, 3, 4, 5};
return arr; // arr is gone after function returns
}
Ignoring return values: Functions like scanf() return important values (number of items read). Ignoring them leads to bugs when input fails.
Parameters and return values are the communication channels between functions. Mastering them — especially understanding pass by value — prepares you for pointers, which unlock the full power of C. Continue with jump statements or proceed to the scope lesson next in the C roadmap.