Functions Basics
Master function fundamentals in C - declaration, definition, parameters, return values, and function prototypes. Learn to organize code into reusable, modular functions.
What Are Functions?
Functions are self-contained blocks of code that perform specific tasks. They're the fundamental building blocks of C programs, enabling code reuse, modularity, and abstraction. Instead of writing the same code repeatedly, you define it once as a function and call it whenever needed. This makes programs shorter, easier to understand, test, and maintain.
Every C program has at least one function: main(), where execution begins. Beyond main(), you create functions to break complex problems into manageable pieces. Well-designed functions do one thing well, have clear names, and interact through defined interfaces (parameters and return values). This principle of modular design is central to professional C programming.
#include <stdio.h>
/* Function to print greeting */
void greet(void) {
printf("Hello, World!\n");
}
/* Function to add two numbers */
int add(int a, int b) {
return a + b;
}
int main(void) {
greet(); // Call greet function
int sum = add(5, 3); // Call add function
printf("Sum: %d\n", sum);
return 0;
}
/* Output:
Hello, World!
Sum: 8
*/Function Declaration vs. Definition
A function declaration (also called prototype) tells the compiler about a function's existence - its name, return type, and parameters. A function definition provides the actual implementation - the code that runs when the function is called. Declarations go in header files or at the top of source files; definitions can be anywhere but typically follow main() or go in separate source files.
#include <stdio.h>
/* Function DECLARATIONS (prototypes) */
int multiply(int x, int y); // Declares multiply function
double calculate_average(int a, int b);
void print_message(void);
/* main function */
int main(void) {
int result = multiply(4, 5);
printf("4 * 5 = %d\n", result);
double avg = calculate_average(10, 20);
printf("Average: %.1f\n", avg);
print_message();
return 0;
}
/* Function DEFINITIONS (implementations) */
int multiply(int x, int y) {
return x * y;
}
double calculate_average(int a, int b) {
return (a + b) / 2.0;
}
void print_message(void) {
printf("Function definitions provide the actual code\n");
}
/* Without prototype (old C style - avoid) */
// If you call a function before defining it without a prototype,
// the compiler assumes it returns int and guesses parameter types.
// This leads to bugs and warnings. Always use prototypes.Function Syntax
Functions follow a consistent syntax: return_type function_name(parameter_list) {body}. The return type specifies what value the function returns (or void for no return value). The function name follows identifier rules. Parameters are variables that receive values when the function is called. The body contains statements that execute when the function runs.
/* Basic function anatomy */
return_type function_name(parameter_type param1, parameter_type param2) {
// Function body
// Local variables
// Statements
return value; // If return_type is not void
}
/* Void function (no return value) */
void display_menu(void) {
printf("1. Option A\n");
printf("2. Option B\n");
printf("3. Exit\n");
}
/* Function with no parameters */
int get_random_number(void) {
return rand();
}
/* Function with parameters */
int max(int a, int b) {
if (a > b) {
return a;
} else {
return b;
}
}
/* Function with multiple return points */
int absolute_value(int n) {
if (n < 0) {
return -n; // Early return for negative
}
return n; // Return for non-negative
}
/* Function returning different types */
double square_root_approximation(double n) {
// Implementation
return result;
}
char get_grade(int score) {
if (score >= 90) return 'A';
if (score >= 80) return 'B';
if (score >= 70) return 'C';
if (score >= 60) return 'D';
return 'F';
}Parameters and Arguments
Parameters are variables listed in the function definition that receive values when the function is called. Arguments are the actual values passed to the function. C uses "call by value" - arguments are copied to parameters, so modifying parameters doesn't affect the original arguments. To modify arguments, you need pointers (covered later).
/* Function with parameters */
int add(int x, int y) { // x and y are parameters
return x + y;
}
int main(void) {
int a = 5, b = 3;
int sum = add(a, b); // a and b are arguments
printf("%d\n", sum);
/* Call by value demonstration */
void modify(int n) {
n = 100; // Modifies local copy only
}
int value = 10;
modify(value);
printf("%d\n", value); // Still 10! Not modified
return 0;
}
/* Multiple parameters */
double calculate_bmi(double weight_kg, double height_m) {
return weight_kg / (height_m * height_m);
}
/* Parameters of different types */
void print_info(char *name, int age, double salary) {
printf("Name: %s\n", name);
printf("Age: %d\n", age);
printf("Salary: $%.2f\n", salary);
}
/* Parameter names in prototype optional */
int multiply(int, int); // Valid (but less clear)
int multiply(int x, int y); // Better (self-documenting)
/* Must match between declaration and definition */
int subtract(int a, int b); // Declaration
int subtract(int a, int b) { // Definition (same types)
return a - b;
}
// int subtract(double a, double b) {"{ }"} // ERROR: Different types!Return Values
The return statement exits a function and optionally returns a value to the caller. The return type in the function signature must match the type of value returned. Void functions don't return values. Functions can have multiple return statements, but only one executes per call. Reaching the end of a non-void function without return is undefined behavior.
/* Function returning int */
int factorial(int n) {
if (n <= 1) {
return 1; // Base case
}
return n * factorial(n - 1); // Recursive case
}
/* Function returning double */
double circle_area(double radius) {
return 3.14159 * radius * radius;
}
/* Function returning char */
char to_uppercase(char c) {
if (c >= 'a' && c <= 'z') {
return c - 32;
}
return c;
}
/* void function (no return value) */
void print_array(int arr[], int size) {
for (int i = 0; i < size; i++) {
printf("%d ", arr[i]);
}
printf("\n");
// No return statement needed (or use: return;)
}
/* Early return for error handling */
int divide(int a, int b) {
if (b == 0) {
printf("Error: Division by zero\n");
return 0; // Early return on error
}
return a / b;
}
/* Using return value */
int main(void) {
int fact = factorial(5);
printf("5! = %d\n", fact);
double area = circle_area(10.0);
printf("Area: %.2f\n", area);
/* Can ignore return value */
factorial(3); // Valid but wasteful
/* Common pattern: Check return value for errors */
FILE *fp = fopen("file.txt", "r");
if (fp == NULL) {
printf("Failed to open file\n");
return 1;
}
return 0;
}
/* WARNING: Undefined behavior */
int bad_function(void) {
int x = 5;
// Missing return statement!
// Undefined what value caller receives
}Function Prototypes
Function prototypes declare functions before they're defined or called. They enable the compiler to check that function calls use the correct number and types of arguments. Prototypes typically go at the top of source files or in header files. Modern C requires prototypes - calling undeclared functions produces warnings or errors.
#include <stdio.h>
/* Prototypes allow calling before definition */
int add(int, int);
int subtract(int, int);
int multiply(int, int);
int main(void) {
/* Can call these functions even though
they're defined below main */
printf("%d\n", add(5, 3));
printf("%d\n", subtract(10, 4));
printf("%d\n", multiply(6, 7));
return 0;
}
/* Definitions after main */
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;
}
/* Without prototypes (causes problems) */
// int main(void) {
// calculate(5); // Compiler doesn't know about calculate!
// return 0;
// }
//
// void calculate(int n) { // Defined after use
// printf("%d\n", n * 2);
// }
// Result: Warning or error, undefined behavior
/* Prototype in header file pattern */
// math_utils.h:
// int add(int a, int b);
// int subtract(int a, int b);
// math_utils.c:
// #include "math_utils.h"
// int add(int a, int b) { return a + b; }
// int subtract(int a, int b) { return a - b; }
// main.c:
// #include "math_utils.h"
// int main(void) {
// int sum = add(5, 3); // Prototype from header
// }Function Best Practices
Well-designed functions make code maintainable and testable. Follow these principles to write professional C functions that others (including future you) can easily understand and use.
/* Practice 1: One function, one purpose */
// Good: Does one thing well
int is_even(int n) {
return n % 2 == 0;
}
// Bad: Does too many things
int process_and_print_and_save(int value) {
int result = value * 2;
printf("%d\n", result);
save_to_file(result);
return result;
}
/* Practice 2: Descriptive names */
// Good: Clear what it does
int calculate_factorial(int n);
double convert_celsius_to_fahrenheit(double celsius);
// Bad: Unclear abbreviations
int calc(int n); // Calculate what?
double conv(double c); // Convert what to what?
/* Practice 3: Limit parameters (max 4-5) */
// Good: Few, clear parameters
void draw_rectangle(int width, int height);
// Bad: Too many parameters
void draw_shape(int x, int y, int width, int height,
int color, int border, int style, int opacity);
// Better: Use a struct
/* Practice 4: Document complex functions */
/**
* Calculates the nth Fibonacci number using recursion.
* @param n The position in the sequence (must be >= 0)
* @return The nth Fibonacci number
*/
int fibonacci(int n) {
if (n <= 1) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
}
/* Practice 5: Validate inputs */
int divide_safe(int a, int b) {
if (b == 0) {
fprintf(stderr, "Error: Division by zero\n");
return 0;
}
return a / b;
}
/* Practice 6: Use const for read-only parameters */
void print_array(const int arr[], int size) {
// arr can't be modified
for (int i = 0; i < size; i++) {
printf("%d ", arr[i]);
}
}
/* Practice 7: Consistent error handling */
// Return 0 for success, negative for error
int initialize(void) {
if (!init_module_a()) return -1;
if (!init_module_b()) return -2;
return 0; // Success
}
/* Practice 8: Keep functions short */
// Aim for < 50 lines. If longer, break into smaller functions
void process_data(void) {
validate_input();
transform_data();
save_results();
}
/* Practice 9: Avoid side effects when possible */
// Good: Pure function (no side effects)
int add(int a, int b) {
return a + b;
}
// Questionable: Modifies global state
int count = 0;
int increment_and_return(void) {
count++; // Side effect
return count;
}Summary & What's Next
Key Takeaways:
- ✅ Functions organize code into reusable blocks
- ✅ Declaration (prototype) vs. definition (implementation)
- ✅ Syntax: return_type name(parameters) {body}
- ✅ C uses call-by-value (arguments are copied)
- ✅ Return statement exits function and returns value
- ✅ void functions don't return values
- ✅ Always use prototypes before calling functions
- ✅ Write functions that do one thing well