PYnative

Python Programming

  • Learn Python
    • Python Tutorials
    • Python Basics
    • Python Interview Q&As
  • Exercises
  • Quizzes
  • Code Editor
Home » C Programming Exercises » C Programming Functions Exercises

C Programming Functions Exercises

Updated on: December 10, 2025 | Leave a Comment

This comprehensive guide offers 25 concept-oriented C Function exercises designed to solidify your understanding. Suitable for both students and experienced developers, the challenges progress from simple definitions to complex areas like function pointers and recursion.

Each exercise includes a Problem Statement, Hint, Solution code, and detailed Explanation, ensuring you don’t just copy code, but genuinely understand how and why it works.

What You’ll Practice

  • Basic Structure: Declaration, calling, and return types.
  • Advanced Topics: Pass by Reference (using pointers), Recursion (including Tail Recursion), and Function Pointers for callbacks.
  • Memory & Scope: Local/Global variables, Dynamic Memory (via malloc), and Storage Classes (static, extern).
  • Optimization: The inline keyword and the pitfalls of Function-like Macros.
  • Complex Parameters: Implementing Variadic Functions (using stdarg.h).

Use Online C Compiler to solve exercises.

+ Table Of Contents (25 Exercises)

Table of contents

  • Exercise 1: Simple Function
  • Exercise 2: Function with Parameters
  • Exercise 3: Function with Return Value
  • Exercise 4: Function Prototype/Declaration
  • Exercise 5: Pass by Value (Integers)
  • Exercise 6: Pass by Reference (Pointers)
  • Exercise 7: Array as a Parameter
  • Exercise 8: Structure as a Parameter
  • Exercise 9: static Local Variable (Lifetime)
  • Exercise 10: Global Variable and Scope
  • Exercise 11: register Storage Class
  • Exercise 12: Function Scope with Helper Function
  • Exercise 13: Basic Recursion (Factorial)
  • Exercise 14: Simple Recursion (Fibonacci Sequence)
  • Exercise 15: Recursion with Base Case
  • Exercise 16: Tail Recursion
  • Exercise 17: Function Pointers
  • Exercise 18: Function as an Argument (Callback)
  • Exercise 19: Function Returning a Pointer
  • Exercise 20: Function Returning a Structure
  • Exercise 21: extern Keyword for Function
  • Exercise 22: inline Function (Optimization Hint)
  • Exercise 23: Function-like Macro (Simple)
  • Exercise 24: Variadic Functions ( … )
  • Exercise 25: Exit Function ( exit() and atexit() )

Exercise 1: Simple Function

Problem Statement: Write a function greet() that takes no arguments and returns nothing (void), and simply prints “Hello, C Functions!”.

+ Hint

The function signature should be void greet(void). Since the function returns nothing, you don’t need a return

+ Show Solution
#include <stdio.h>

// Function definition
void greet(void) {
    printf("Hello, C Functions!\n");
}

int main() {
    // Function call
    greet();

    return 0;
}Code language: C++ (cpp)
Run

Explanation:

This is the most basic function structure.

  • Signature: void greet(void) specifies that the function takes no arguments (void inside the parentheses) and returns no value (void before the function name).
  • Function Call: In main(), greet(); transfers program execution to the function definition.
  • Execution: The function executes the printf statement and control returns to the line immediately following the function call in main.

Exercise 2: Function with Parameters

Problem Statement: Create a function add_numbers() that takes two integers as parameters, calculates their sum, and prints the result inside the function. Call this function from main with two different numbers.

Sample Output:

The sum of 15 and 7 is: 22
The sum of 100 and 25 is: 125
+ Hint

Use the parameters a and b directly within the function body to perform the addition. Since the printing is done inside the function, the return type remains void.

+ Show Solution
#include <stdio.h>

// Function definition with parameters a and b
void add_numbers(int a, int b) {
    int sum = a + b;
    printf("The sum of %d and %d is: %d\n", a, b, sum);
}

int main() {
    int x = 15;
    int y = 7;

    // Function call passing values (arguments)
    add_numbers(x, y); 

    // Another call with literal values
    add_numbers(100, 25); 

    return 0;
}Code language: C++ (cpp)
Run

Explanation:

This exercise demonstrates parameter passing.

  • Parameters vs. Arguments: a and b are the parameters (placeholders) defined in the function signature. x and y (or 100 and 25) are the arguments (actual values) passed during the call.
  • Passing: When add_numbers(x, y) is called, the value of x (15) is copied into parameter a, and the value of y (7) is copied into parameter b. The function then works with these copies.

Exercise 3: Function with Return Value

Problem Statement: Implement a function multiply() that takes two integers and returns their product. Call the function from main and store the result in a variable before printing it

Sample Output:

8*6 is 48.
+ Hint

The return type must match the type of the value being returned (which is an int product). The main function must use an assignment operator (=) to capture the value the function returns.

+ Show Solution
#include <stdio.h>

// Function returns an integer (int)
int multiply(int x, int y) {
    int product = x * y;
    return product; // Return the calculated value
}

int main() {
    int num1 = 8;
    int num2 = 6;
    int result;

    // Function call: the returned value is stored in 'result'
    result = multiply(num1, num2); 

    printf("%d multiplied by %d is %d.\n", num1, num2, result);

    return 0;
}Code language: C++ (cpp)
Run

Explanation:

This exercise shows how functions can calculate a value and send it back to the caller.

  • Return Type: The function signature starts with int to indicate it returns an integer.
  • return Statement: return product; immediately stops the function’s execution and sends the value stored in product back to the point of the call.
  • Receiving Value: In main, result = multiply(num1, num2); is essential. The value 48 (the product) is returned by the function, and the assignment operator stores this value in the result variable.

Exercise 4: Function Prototype/Declaration

Problem Statement: Write a program where the function definition for is_even(int num) (which returns 1 if even, 0 otherwise) is placed after the main function. This setup requires a function prototype (declaration) before main.

+ Hint

The prototype is just the function signature followed by a semicolon (;). Place it near the top of the file, before the main function.

+ Show Solution
#include <stdio.h>

// 1. Function Prototype (Declaration)
// This tells the compiler the function exists, its signature, and return type.
int is_even(int num); 

int main() {
    int check_num = 14;

    if (is_even(check_num)) { // Function call
        printf("%d is even.\n", check_num);
    } else {
        printf("%d is odd.\n", check_num);
    }

    return 0;
}

// 2. Function Definition (Must match the prototype exactly)
int is_even(int num) {
    if (num % 2 == 0) {
        return 1; // True
    } else {
        return 0; // False
    }
}Code language: C++ (cpp)
Run

Explanation:

C compilers read code sequentially from top to bottom.

  • The Need for a Prototype: When the compiler encounters the call is_even(check_num) inside main, it needs to know what is_even is. If the definition is below main, the compiler hasn’t seen it yet and will produce an error.
  • The Prototype: int is_even(int num); acts as a forward reference. It informs the compiler that a function named is_even exists, takes one int argument, and returns an int. This satisfies the compiler at the time of the call in main. The actual function body (definition) can then appear anywhere later in the file.

Exercise 5: Pass by Value (Integers)

Problem Statement: Write a function increment(int n) that takes an integer, increments its value by 1 inside the function, and prints the variable’s value before and after the function call in main to demonstrate that the original variable is unchanged by the function

+ Hint

Since C passes arguments by value, the function receives a copy. Changes made to the parameter n inside increment will not affect the original variable in main.

+ Show Solution
#include <stdio.h>

// Function receives a COPY of the argument
void increment(int n) {
    printf("Inside function: Value before increment: %d\n", n);
    n = n + 1; // Only the copy is modified
    printf("Inside function: Value after increment: %d\n", n);
}

int main() {
    int number = 10;

    printf("In main: Before function call, number = %d\n", number);

    increment(number); // The value 10 is copied to the 'n' parameter

    printf("In main: After function call, number = %d\n", number); 

    return 0;
}Code language: C++ (cpp)
Run

Explanation:

This exercises illustrates Pass by Value, the default mechanism for non-array variables in C.

  • Copy Created: When increment(number) is called, a temporary storage location (n) is created, and the value of number (10) is copied into it.
  • Isolation: The operation n = n + 1; only changes the local copy n to 11.
  • No Effect: The original variable number in the main function remains unchanged at 10, proving the function operates on an isolated copy.

Exercise 6: Pass by Reference (Pointers)

Problem Statement: Write a function swap(int *a, int *b) that uses pointers to swap the values of two integer variables passed from main, demonstrating Pass by Reference.

+ Hint

The parameters must be pointers to integers (int *). Inside the function, you must use the dereference operator (*) to access and modify the actual values stored at the memory addresses.

+ Show Solution
#include <stdio.h>

// Function takes pointers (memory addresses) as arguments
void swap(int *a, int *b) {
    int temp;

    // Dereference: Read the value at address 'a'
    temp = *a; 

    // Dereference: Write the value at address 'b' to address 'a'
    *a = *b; 

    // Dereference: Write the value in 'temp' to address 'b'
    *b = temp;

    printf("Inside swap: values are now %d and %d\n", *a, *b);
}

int main() {
    int x = 10;
    int y = 20;

    printf("In main: Before swap, x = %d, y = %d\n", x, y);

    // Pass the ADDRESSES of x and y using the address-of operator (&)
    swap(&x, &y); 

    printf("In main: After swap, x = %d, y = %d\n", x, y); 

    return 0;
}Code language: C++ (cpp)
Run

Explanation:

Pass by Reference (simulated in C using pointers) allows a function to modify variables declared in the caller’s scope.

  • Passing: The & operator (address-of) passes the memory addresses of x and y to the function.
  • Receiving: The parameters *a and *b store these addresses.
  • Modification: The * operator (dereference) is used inside swap to follow the address and read/write the value in the calling function’s memory. This directly changes x and y, hence the term “Pass by Reference.”

Exercise 7: Array as a Parameter

Problem Statement: Create a function find_max(int arr[], int size) that takes an integer array and its size as parameters and returns the largest element found in the array.

Given:

int numbers[] = {45, 12, 88, 5, 92, 33};Code language: C++ (cpp)

Expected Output:

The largest element in the array is: 92
+ Hint

Arrays are always passed by reference (using a pointer to the first element) in C. Loop through the array from the second element onward, comparing each element to a running max variable.

+ Show Solution
#include <stdio.h>

// The array parameter decays to a pointer (int *arr)
int find_max(int arr[], int size) {
    if (size <= 0) return 0; // Handle empty array case

    int max = arr[0]; // Assume the first element is the largest
    int i;

    for (i = 1; i < size; i++) {
        if (arr[i] > max) {
            max = arr[i]; // Update max if a larger element is found
        }
    }

    return max;
}

int main() {
    int numbers[] = {45, 12, 88, 5, 92, 33};
    int array_size = sizeof(numbers) / sizeof(numbers[0]);
    int max_val;

    max_val = find_max(numbers, array_size);

    printf("The largest element in the array is: %d\n", max_val);

    return 0;
}Code language: C++ (cpp)
Run

Explanation:

When an array name is used as a function argument, C passes the address of its first element.

  • Array Decay: Even though the parameter is declared as int arr[], the compiler treats it as int *arr. This is why the size must be passed separately, as the function cannot determine the array size from the pointer alone.
  • Efficiency: Passing arrays this way is efficient because the function receives only a memory address (a pointer), not a copy of the entire array (unlike standard Pass by Value). The function can directly access and potentially modify the elements of the original array.

Exercise 8: Structure as a Parameter

Problem Statement: Define a structure struct Point {int x; int y;}. Write a function print_point(struct Point p) that takes an instance of this structure by value and prints its coordinates in the format (x, y).

+ Hint

Unlike arrays, structures are passed by value by default. Use the dot operator (.) inside the function to access the structure members of the copy passed to the function.

+ Show Solution
#include <stdio.h>

// Structure definition
struct Point {
    int x;
    int y;
};

// Function takes the structure BY VALUE (receives a full copy)
void print_point(struct Point p) {
    // Access members using the dot operator
    printf("Inside function: Point coordinates are: (%d, %d)\n", p.x, p.y);

    // If we change the copy, the original is unaffected:
    p.x = 999; 
}

int main() {
    // Initialize a Point structure
    struct Point start_point = {10, 20}; 

    printf("In main: Before function call, x=%d\n", start_point.x);

    // Pass the structure by value
    print_point(start_point); 

    printf("In main: After function call, x=%d y=%d (original is unchanged)\n", start_point.x, start_point.y); 

    return 0;
}Code language: C++ (cpp)
Run

Explanation:

This exercise demonstrates the default Pass by Value mechanism for structures.

  • Copy Passed: When print_point(start_point) is called, a complete copy of the start_point structure is created and passed to the parameter p.
  • Isolation: Any modification made to the members of p (like setting p.x = 999) only affects the local copy within the function and leaves the original start_point in main intact.
  • Alternative (Not Shown): If the intent were to modify the original structure, the function would need to take a pointer to the structure (struct Point *p) instead, simulating Pass by Reference.

Exercise 9: static Local Variable (Lifetime)

Problem Statement: Write a function test_static_scope() that declares a count a static local variable (static int count = 1;). Increment it by 1 inside a function. Call the function twice to show that the variable’s value is retained across function calls.

Expected Output:

--- First Call ---
Static count retained value: 1
Static count after increment: 2

--- Second Call ---
Static count retained value: 2
Static count after increment: 3
+ Hint

The static storage class changes the lifetime of the local variable from automatic to the entire duration of the program. The initialization (= 1) only happens once.

+ Show Solution
#include <stdio.h>

void test_static_lifetime() {
    // 'static' changes the variable's lifetime.
    // Initialization to 1 happens ONLY on the first call.
    static int count = 1; 

    printf("Static count retained value: %d\n", count);
    count++; // This modification persists
    printf("Static count after increment: %d\n", count);
}

int main() {
    printf("--- First Call ---\n");
    test_static_lifetime(); 

    printf("\n--- Second Call ---\n");
    test_static_lifetime(); // 'count' retains the value 2 from the previous call

    return 0;
}Code language: C++ (cpp)
Run

Explanation:

The static storage class is used here to alter the variable’s lifetime.

  • Lifetime Extended: A static local variable is created and initialized only once (when the program starts, or the first time control flow passes over its declaration). It maintains its value throughout the execution of the program.
  • Scope Maintained: Crucially, even though its lifetime is extended, its scope remains local to the function. It cannot be accessed directly from main. This is often used for counters or flags that need to track function usage internally.

Exercise 10: Global Variable and Scope

Problem Statement: Define a global variable int global_counter = 0; outside of all functions. Write a function update_global_counter() that increments it by 1 and prints its value. Demonstrate how functions can modify global variables and how both main and the function can access it.

Expected Output:

In main: Initial global counter = 0
Inside function: Global counter updated to 1
Inside function: Global counter updated to 2
In main: Final global counter = 2
+ Hint

Global variables have file scope (or external linkage by default) and a program lifetime. They can be accessed by name from any function defined after their declaration without any special declaration inside the function.

+ Show Solution
#include <stdio.h>

// Global variable: accessible by all functions defined below it
int global_counter = 0; 

void update_global_counter() {
    global_counter++; // Directly access and modify the global variable
    printf("Inside function: Global counter updated to %d\n", global_counter);
}

int main() {
    printf("In main: Initial global counter = %d\n", global_counter);

    update_global_counter(); 
    update_global_counter(); 

    // main can also access the current value directly
    printf("In main: Final global counter = %d\n", global_counter); 

    return 0;
}Code language: C++ (cpp)
Run

Explanation:

Global variables are declared outside all function bodies.

  • Scope: They have file scope, meaning they are visible from their point of declaration to the end of the file.
  • Lifetime: They have a program lifetime, meaning they exist for the entire duration of the program.
  • Access: Any function in the file can directly read and modify a global variable, which is often considered bad practice as it reduces modularity and makes code harder to debug (side effects are difficult to track).

Exercise 11: register Storage Class

Problem Statement: Briefly discuss the intended use of the register storage class for a function parameter and apply it to a simple parameter in a function sum_two(register int a, register int b).

Expected Output:

a:50 b:75
Sum: 125
+ Hint

The register keyword is a suggestion to the compiler to store the variable in a CPU register instead of main memory for faster access. Use it on variables that will be heavily used within a function (like loop counters).

+ Show Solution
#include <stdio.h>

// 'register' is a hint to the compiler for potential optimization
int sum_two(register int a, register int b) {
    // These variables are candidates for being stored in CPU registers
    printf("a:%d b:%d\n", a, b);
    return a + b; 
}

int main() {
    int result = sum_two(50, 75);
    printf("Sum: %d\n", result);

    return 0;
}

/*
Explanation (as part of the output):
The 'register' keyword suggests to the compiler that 'a' and 'b' should be 
stored in a CPU register for fast access. In modern compilers, this keyword 
is often ignored or automatically optimized where appropriate, as compilers 
are usually better at register allocation than manual hints.
*/Code language: C++ (cpp)
Run

Explanation:

The register storage class is primarily a performance hint.

  • Purpose: It suggests that the variable will be accessed frequently and should ideally be stored in a CPU register. Accessing registers is significantly faster than accessing main memory (RAM).
  • Modern C: Modern compilers are highly optimized. They often ignore the register keyword and decide register allocation themselves, making its use mostly historical or academic. You cannot take the address of a register variable using &, because a register does not have a conventional memory address.

Exercise 12: Function Scope with Helper Function

Problem Statement: Write a function calculate_net_salary(float gross) which internally calls a private helper function calculate_tax(float income) (declared and defined within the same file) to illustrate modularity. Assume a fixed tax rate of 15%.

Expected Output:

Inside main tax function
Inside Private helper function
Gross Salary: 50000.00
Tax Deducted: 7500.00
Net Salary Received: 42500.00
+ Hint

Define the helper function first, then the main function that calls it. This improves readability by placing sub-tasks into separate, easily understood functions.

+ Show Solution
#include <stdio.h>

// Private helper function definition
float calculate_tax(float income) {
    print("Inside Private helper function")
    const float TAX_RATE = 0.15;
    return income * TAX_RATE;
}

// Main function definition, which uses the helper
float calculate_net_salary(float gross) {
    print("Inside main tax function")
    float tax_amount = calculate_tax(gross);
    float net_salary = gross - tax_amount;

    printf("Gross Salary: %.2f\n", gross);
    printf("Tax Deducted: %.2f\n", tax_amount);

    return net_salary;
}

int main() {
    float gross_income = 50000.00;
    float net = calculate_net_salary(gross_income);

    printf("Net Salary Received: %.2f\n", net);

    return 0;
}Code language: C++ (cpp)
Run

Explanation:

This exercise demonstrates functional decomposition and modularity.

  • Modularity: By separating the tax calculation logic into a dedicated helper function (calculate_tax), the main function (calculate_net_salary) becomes cleaner and easier to read, focusing only on the high-level steps (Gross – Tax = Net).
  • Reusability: The calculate_tax function could be reused by other functions in the program if they also needed to calculate tax on income, improving code reuse. This organization makes the code base more maintainable.

Exercise 13: Basic Recursion (Factorial)

Problem Statement: Write a recursive function factorial(int n) to calculate the factorial of a non-negative integer (n!). The definition is n!=n×(n−1)! and 0!=1.

A factorial, represented by an exclamation point (!), is the product of all positive integers up to a given non-negative integer, written as n!

Formula: For a non-negative integer n, the factorial is defined as:

n! = n × (n - 1) × (n - 2) × ... × 1

Examples:

0! = 1
1! = 1
3! = 3 × 2 × 1 = 6
5! = 5 × 4 × 3 × 2 × 1 = 120
+ Hint

Every recursive function needs two things:

  1. A Base Case: The condition that stops the recursion (n == 0).
  2. A Recursive Step: The call to the function itself, usually with a reduced parameter (n * factorial(n - 1)).
+ Show Solution
#include <stdio.h>

// Recursive function for factorial
long long factorial(int n) {
    // Base Case: Stops the recursion
    if (n == 0) {
        return 1;
    }

    // Recursive Step: Calls itself with n-1
    return n * factorial(n - 1); 
}

int main() {
    int num = 5;
    long long result = factorial(num);

    printf("Factorial of %d is: %lld\n", num, result); // 5! = 120

    return 0;
}Code language: C++ (cpp)
Run

Explanation:

Recursion is the process where a function calls itself.

  • Base Case (n=0): When the function hits the base case, it returns 1, preventing infinite calls.
  • Stacking Calls: For factorial(5), the calls stack up: 5×fact(4)→5×(4×fact(3))→⋯→5×4×3×2×1×fact(0).
  • Unwinding: Once fact(0) returns 1, the multiplication results are returned back up the stack (unwinding) until the final result is returned to main. This process uses the program’s call stack to store intermediate results.

Exercise 14: Simple Recursion (Fibonacci Sequence)

Problem Statement: Implement a recursive function fibonacci(int n) to find the 7-th number in the Fibonacci sequence (0,1,1,2,3,5,8,13…).

Fibonacci sequence: It is a series of numbers where each number is the sum of the two preceding ones, typically starting with 0 and 1. The sequence goes 0, 1, 1, 2, 3, 5, 8, 13, 21, and so on, and is represented by the recursive formula F(n) = F(n-1) + F(n-2), with F(0)=0 and F(1)=1.

Expected Output:

The 7-th Fibonacci number is: 13

+ Hint

The recursive definition is: fib(n)=fib(n−1)+fib(n−2). You will need two base cases to stop the recursion: n=0 returns 0, and n=1 returns 1.

+ Show Solution
#include <stdio.h>

// Recursive function for Fibonacci
int fibonacci(int n) {
    // Base Cases:
    if (n == 0) {
        return 0;
    }
    if (n == 1) {
        return 1;
    }

    // Recursive Step: Sum of the previous two terms
    return fibonacci(n - 1) + fibonacci(n - 2); 
}

int main() {
    int n = 7;
    int result = fibonacci(n);

    printf("The %d-th Fibonacci number is: %d\n", n, result); // 7th term is 13

    return 0;
}Code language: C++ (cpp)
Run

Explanation:

This recursive solution directly implements the mathematical definition of the Fibonacci sequence.

  • Dual Base Cases: Since the definition relies on two previous terms, we need base cases for both n=0 and n=1 to ensure the recursion terminates correctly.
  • Complexity: While elegant, this recursive implementation is highly inefficient for large n. It recalculates the same Fibonacci numbers repeatedly, leading to exponential time complexity (O(2n)), which is a common trade-off when using simple recursion.

Exercise 15: Recursion with Base Case

Problem Statement: Write a recursive function sum_digits(int n) that finds the sum of the digits of an integer (e.g., 123→1+2+3=6), clearly showing the base case.

Expected Output:

The sum of digits of 478 is: 19
+ Hint

Use the modulo operator (% 10) to get the last digit and integer division (/ 10) to remove it. The base case is when the number becomes 0.

+ Show Solution
#include <stdio.h>

// Recursive function to sum digits
int sum_digits(int n) {
    // Base Case: When the number is reduced to 0, stop and return 0
    if (n == 0) {
        return 0;
    }

    // Recursive Step:
    // 1. Get the last digit (n % 10)
    // 2. Add it to the recursive call result with the number reduced (n / 10)
    return (n % 10) + sum_digits(n / 10);
}

int main() {
    int num = 478;
    int result = sum_digits(num); // 4 + 7 + 8 = 19

    printf("The sum of digits of %d is: %d\n", num, result);

    return 0;
}Code language: C++ (cpp)
Run

Explanation:

This models the problem as an accumulation across recursive calls.

  • Logic: For 478: it calculates 8+sum_digits(47). sum_digits(47) calculates 7+sum_digits(4). sum_digits(4) calculates 4+sum_digits(0).
  • Base Case: sum_digits(0) returns 0.
  • Unwinding: The results add up as the calls unwind: 8+7+4+0=19. The base case is essential for terminating the chain and providing the final zero value to start the sum.

Exercise 16: Tail Recursion

Problem Statement: Try to implement the factorial function using tail recursion (where the recursive call is the last operation in the function). This typically requires a helper function or an accumulator parameter.

Expected Output:

Factorial of 5 (Tail Recursion) is: 120
+ Hint

Define a helper function, usually tail_factorial(int n, int accumulator). The accumulator holds the running result. The recursive call should return the result of the next recursive call directly, with no operation applied to it afterward.

+ Show Solution
#include <stdio.h>

// Helper function that is tail-recursive
long long tail_factorial(int n, long long accumulator) {
    // Base Case: Return the accumulated result
    if (n == 0) {
        return accumulator;
    }

    // Tail Recursive Step: The recursive call is the last action
    return tail_factorial(n - 1, accumulator * n);
}

// Wrapper function for user convenience
long long factorial_tail(int n) {
    return tail_factorial(n, 1); // Start accumulator at 1
}

int main() {
    int num = 5;
    long long result = factorial_tail(num);

    printf("Factorial of %d (Tail Recursion) is: %lld\n", num, result);

    return 0;
}Code language: C++ (cpp)
Run

Explanation:

Tail recursion is a special form of recursion that allows modern compilers to perform an optimization called tail-call elimination.

  • Mechanism: In the tail-recursive version, the calculation (n×accumulator) is performed before the recursive call, and the result is passed as an argument (accumulator * n) to the next call. The last operation is simply the recursive call itself.
  • Optimization: When optimized, the compiler can reuse the existing stack frame for the next call, turning the recursion into an efficient iterative loop, thereby preventing potential stack overflow issues that can occur with deep, non-tail recursion.

Exercise 17: Function Pointers

Problem Statement: Define a function pointer that can point to a function taking two integers and returning an integer. Assign it to the multiply function (from Exercise 3) and call the function using the pointer.

Expected Output:

a:10 b:5
Result of multiplication (via function pointer): 50
+ Hint

The syntax for declaring a function pointer matches the signature of the function it points to: return_type (*pointer_name)(parameter_list). Use the address-of operator (&) to get the function’s address (though it’s often optional for functions). Calling it looks like a normal function call: (*pointer_name)(arg1, arg2).

+ Show Solution
#include <stdio.h>

int multiply(int x, int y) {
    return x * y;
}

int main() {
    // 1. Declare a function pointer 'ptr_func'
    // It points to a function that takes (int, int) and returns int.
    int (*ptr_func)(int, int); 

    // 2. Assign the address of the 'multiply' function to the pointer
    ptr_func = multiply; // Or ptr_func = &multiply;

    int a = 10, b = 5;
    printf("a:%d b:%d\n", a, b);
    int result;

    // 3. Call the function using the pointer (dereferencing it)
    result = (*ptr_func)(a, b); 

    // Alternative call syntax (often cleaner)
    // result = ptr_func(a, b); 

    printf("Result of multiplication (via function pointer): %d\n", result); // 50

    return 0;
}Code language: C++ (cpp)
Run

Explanation:

Function Pointers allow you to treat functions as ordinary data, enabling dynamic function calls.

  • Declaration: The parentheses around *ptr_func are mandatory to distinguish it from a function that returns a pointer.
  • Assignment: Since the multiply function’s signature matches the pointer’s signature, its address can be assigned to ptr_func. The compiler handles functions’ names as pointers to their starting address.
  • Invocation: Calling the function via the pointer, (*ptr_func)(a, b), dereferences the pointer to execute the code at that memory address, just like a regular function call.

Exercise 18: Function as an Argument (Callback)

Problem Statement: Write a function operate_on_data(int a, int b, int (*op)(int, int)) that takes two integers and a function pointer as arguments. Pass either an add(int, int) or a subtract(int, int) function to it, demonstrating a callback mechanism.

Expected Output:

x:50 y:15
Operation (Add): 65
Operation (Subtract): 35
+ Hint

The third parameter in operate_on_data is the function pointer declaration itself. The function body should call the provided function pointer using the arguments a and b.

+ Show Solution
#include <stdio.h>

// Functions to be used as callbacks
int add(int x, int y) { return x + y; }
int subtract(int x, int y) { return x - y; }

// Function that takes a function pointer as a parameter
int operate_on_data(int a, int b, int (*op)(int, int)) {
    // 'op' is the function pointer; call it directly
    return op(a, b); 
}

int main() {
    int x = 50, y = 15;
    printf("x:%d y:%d\n", x, y);
    int result_add, result_sub;

    // Pass 'add' function (address)
    result_add = operate_on_data(x, y, add);
    printf("Operation (Add): %d\n", result_add); // 65

    // Pass 'subtract' function (address)
    result_sub = operate_on_data(x, y, subtract);
    printf("Operation (Subtract): %d\n", result_sub); // 35

    return 0;
}Code language: C++ (cpp)
Run

Explanation:

This is the basis of callback functions and polymorphism in C.

  • Mechanism: The operate_on_data function doesn’t care how the operation is performed; it just executes the function provided by the op pointer parameter.
  • Flexibility: By changing which function address is passed (add or subtract), you change the behavior of operate_on_data without modifying its source code. This is fundamental for implementing things like generic sorting algorithms (where the comparison function is a callback) or event handling.

Exercise 19: Function Returning a Pointer

Problem Statement: Write a function create_int(int value) that dynamically allocates memory for an integer using malloc, initializes it, and returns a pointer to that integer. Remember to free the memory in main.

+ Hint

The return type of the function must be int *. Inside the function, use malloc(sizeof(int)) to allocate memory, and return the pointer returned by malloc.

+ Show Solution
#include <stdio.h>
#include <stdlib.h> // Required for malloc() and free()

// Function returns a pointer to an integer (int *)
int* create_int(int value) {
    // Allocate memory for one integer
    int *ptr = (int *)malloc(sizeof(int)); 

    if (ptr == NULL) {
        fprintf(stderr, "Memory allocation failed.\n");
        return NULL;
    }

    // Initialize the dynamically allocated memory
    *ptr = value; 

    return ptr; // Return the address of the new integer
}

int main() {
    int initial_value = 42;
    int *my_dynamic_int;

    // Call function, receiving the memory address
    my_dynamic_int = create_int(initial_value); 

    if (my_dynamic_int != NULL) {
        printf("Value created dynamically: %d\n", *my_dynamic_int);

        // Clean up: Free the dynamically allocated memory
        free(my_dynamic_int); 
        my_dynamic_int = NULL;
    }

    return 0;
}Code language: C++ (cpp)
Run

Explanation:

Functions returning pointers are essential for dynamic memory management.

  • Return Type: int * indicates the function returns the memory address of an integer.
  • Heap Memory: malloc allocates memory from the heap, which means the memory persists even after the create_int function returns (unlike stack memory for local variables).
  • Safety: The function returns a valid address that main can use. If malloc fails, returning NULL is the standard way to handle the error. The caller (main) is then responsible for freeing this memory to prevent a memory leak.

Exercise 20: Function Returning a Structure

Problem Statement: Define a struct Complex {float real; float imag;}. Write a function add_complex(struct Complex a, struct Complex b) that takes two complex numbers by value and returns the resulting Complex structure (sum of real parts and sum of imaginary parts).

+ Hint

The return type of the function should be struct Complex. Create a local instance of the structure, calculate the result, and return the local structure instance.

+ Show Solution
#include <stdio.h>

struct Complex {
    float real;
    float imag;
};

// Function returns a structure
struct Complex add_complex(struct Complex a, struct Complex b) {
    struct Complex result; // Local structure instance

    result.real = a.real + b.real;
    result.imag = a.imag + b.imag;

    return result; // Returns a COPY of the local structure
}

void print_complex(struct Complex c) {
    printf("%.2f + %.2fi\n", c.real, c.imag);
}

int main() {
    struct Complex z1 = {2.5, 3.0};
    struct Complex z2 = {1.5, 4.5};
    struct Complex z_sum;

    printf("z1 = "); print_complex(z1);
    printf("z2 = "); print_complex(z2);

    // Function call: receives the returned structure copy
    z_sum = add_complex(z1, z2); 

    printf("Sum = "); print_complex(z_sum); // 4.00 + 7.50i

    return 0;
}Code language: C++ (cpp)
Run

Explanation:

C allows functions to return entire structure instances by value.

  • Efficiency Note: For very large structures, passing and returning structures by value can be inefficient due to the large amount of copying involved. In such cases, it is often better to pass and return pointers to structures.
  • Mechanism: When add_complex returns result, the entire structure is copied onto the stack and then copied into the destination variable (z_sum) in main.

Exercise 21: extern Keyword for Function

Problem Statement: Write a two-file program where the function definition is in one file (helper.c) and it’s called from the main file (main.c) using the extern keyword (typically implied via an included header file) to link the function, demonstrating external linkage.

+ Hint

In C, function declarations are extern by default, so explicitly using the keyword is usually for clarity. You’ll need two separate files and compile them together. The function prototype in the main file (or header) tells the compiler that the function definition exists elsewhere.

+ Show Solution

This solution is conceptual, requires two files.

File 1: helper.c (The definition)

// helper.c
#include <stdio.h>

// Function definition (defaults to external linkage)
int calculate_area_ext(int length, int width) {
    return length * width;
}Code language: C++ (cpp)

File 2: main.c (The caller)

// main.c
#include <stdio.h>

// Explicit prototype with extern (optional, but shows intent)
// This tells the linker to look for the definition in another file.
extern int calculate_area_ext(int length, int width); 

int main() {
    int result = calculate_area_ext(10, 5);
    printf("Area calculated externally: %d\n", result);
    return 0;
}

/*
To compile this:
gcc main.c helper.c -o program
./program
*/Code language: C++ (cpp)
Run

Explanation:

The extern storage class and prototypes enable separate compilation and linking.

  • External Linkage: By default, non-static functions have external linkage, meaning they are visible across multiple object files.
  • Compiler vs. Linker: When main.c is compiled, the prototype tells the compiler the function signature, allowing the call to be validated. The compiler assumes the function is defined elsewhere. The linker then takes the object files for main.c and helper.c and resolves the function call by connecting it to the actual code block in helper.c. Explicitly using extern on the prototype emphasizes this external linkage.

Exercise 22: inline Function (Optimization Hint)

Problem Statement: Write a simple, short function like get_min(int a, int b) and declare it as inline to suggest to the compiler that it should replace the function call with the function body at the call site, often called inlining.

+ Hint

The inline keyword is a request, not a command. It is typically used for short functions to eliminate the overhead of the function call (stack setup, parameter passing, return jump).

+ Show Solution
#include <stdio.h>

// 'inline' keyword suggests replacing the call with the body
inline int get_min(int a, int b) {
    return (a < b) ? a : b;
}

int main() {
    int x = 12;
    int y = 8;

    // When compiled, the compiler might replace this line with:
    // int result = (x < y) ? x : y;
    int result = get_min(x, y); 

    printf("The minimum of %d and %d is: %d\n", x, y, result);

    return 0;
}Code language: C++ (cpp)
Run

Explanation:

The inline keyword is an optimization hint that minimizes function call overhead.

  • Inlining: Instead of creating a separate block of code for get_min and jumping to it every time it’s called, the compiler copies the function’s body directly into the calling function’s code at the point of the call.
  • Trade-offs: This can improve speed by removing call overhead but can increase the overall program size (code bloat) if the function is called many times or if the function body is large. It is typically best reserved for very small, frequently used functions.

Exercise 23: Function-like Macro (Simple)

Problem Statement: Define a simple function-like macro SQUARE(x) that calculates x×x. Demonstrate a simple use case.

+ Hint

Macros are defined using #define. To prevent unexpected behavior due to operator precedence, always enclose the entire macro definition and all its arguments in parentheses.

+ Show Solution
#include <stdio.h>

// Function-like macro definition: everything is wrapped in parentheses for safety
#define SQUARE(x) ((x) * (x))

int main() {
    int a = 5;
    int b = 3;
    int result_a, result_b;

    // Preprocessor replaces SQUARE(a) with ((a) * (a))
    result_a = SQUARE(a); 
    printf("SQUARE(%d) = %d\n", a, result_a); // 25

    // Preprocessor replaces SQUARE(b + 1) with ((b + 1) * (b + 1))
    result_b = SQUARE(b + 1); 
    printf("SQUARE(%d + 1) = %d\n", b, result_b); // 16

    return 0;
}Code language: C++ (cpp)
Run

Explanation:

Macros are processed by the C preprocessor before compilation.

  • Text Substitution: A macro is a simple text replacement. When the preprocessor sees SQUARE(a), it literally replaces it with ((a) * (a)).
  • Safety: The parentheses are vital. If the macro were defined as #define SQUARE(x) x * x, the call SQUARE(b + 1) would expand to b + 1 * b + 1, which evaluates to 3+3+1=7 (due to operator precedence), which is incorrect. Wrapping ensures the intended arithmetic grouping.

Exercise 24: Variadic Functions (...)

Problem Statement: Write a simple variadic function (using stdarg.h, va_list, etc.) called sum_all(int count, ...) that takes an initial integer indicating the number of subsequent arguments and calculates the sum of that variable number of integers.

+ Hint

You must include <stdarg.h>. Use va_list to declare a list variable, va_start to initialize it using the last fixed argument (count), va_arg to extract each argument, and va_end to clean up.

+ Show Solution
#include <stdio.h>
#include <stdarg.h> // Required for variadic functions

// The '...' indicates a variable number of arguments
int sum_all(int count, ...) {
    va_list args; // Declare a list to hold the variable arguments
    int sum = 0;
    int i, num;

    // Initialize args list, starting after the last fixed argument ('count')
    va_start(args, count); 

    for (i = 0; i < count; i++) {
        // Extract the next argument, specifying it's an 'int'
        num = va_arg(args, int); 
        sum += num;
    }

    // Clean up the va_list structure
    va_end(args); 

    return sum;
}

int main() {
    // Calling with 3 numbers: 10 + 20 + 30 = 60
    int result1 = sum_all(3, 10, 20, 30); 
    printf("Sum of 3 numbers: %d\n", result1); 

    // Calling with 5 numbers: 1 + 2 + 3 + 4 + 5 = 15
    int result2 = sum_all(5, 1, 2, 3, 4, 5);
    printf("Sum of 5 numbers: %d\n", result2); 

    return 0;
}Code language: C++ (cpp)
Run

Explanation:

Variadic functions (like printf) allow a function to accept an indefinite number of arguments.

  • va_end: Cleans up the memory associated with args before the function returns.
  • count: A variadic function must have at least one fixed argument (count here) to tell the function how many arguments follow or what their types are.
  • va_start: Initializes the pointer (args) to the beginning of the variable argument list.
  • va_arg: Retrieves the next argument in the list. The second parameter (int in this case) is crucial because the function must explicitly know the type of the data being extracted.

Exercise 25: Exit Function (exit() and atexit())

Problem Statement: Write a program that registers a cleanup function using atexit() that prints a “Cleanup performed” message, and then calls the exit() function from within another function to demonstrate controlled program termination and cleanup.

+ Hint

Both exit() and atexit() require the <stdlib.h> header. atexit takes a pointer to a function that takes no arguments and returns void. The cleanup function will run automatically when exit() is called.

+ Show Solution
#include <stdio.h>
#include <stdlib.h> // Required for atexit() and exit()

// The cleanup function (must take void and return void)
void cleanup_handler(void) {
    printf("[atexit] Cleanup performed: Shutting down resources.\n");
}

// Function that forces program termination
void terminate_program(int status) {
    printf("[terminate_program] Calling exit() with status %d...\n", status);
    // This terminates the program immediately.
    // Registered atexit handlers are called automatically.
    exit(status); 
}

int main() {
    int error_code = 1;

    // Register the cleanup function. 
    // It will be called when exit() is executed.
    if (atexit(cleanup_handler) != 0) {
        fprintf(stderr, "Could not register atexit handler.\n");
        return 1;
    }

    printf("[main] Program execution starting.\n");

    terminate_program(error_code);

    // This line will NEVER be reached because exit() terminated the program
    printf("[main] This line is after exit() and will not print.\n"); 

    return 0; // The program never gets here
}Code language: C++ (cpp)
Run

Explanation:

These functions provide explicit control over a program’s lifetime.

  • atexit(): Registers a function (the exit handler or cleanup function) to be called automatically upon normal program termination (when main returns or when exit() is called). This is essential for closing files, releasing locks, or freeing global resources.
  • exit(): Causes immediate, unconditional termination of the program. Unlike returning from main, it stops execution wherever it’s called. Crucially, it flushes output buffers and calls all functions registered by atexit() before the process ends. (Note: _exit() or _Exit() terminate immediately without calling atexit() handlers.)

Filed Under: C Programming Exercises

Did you find this page helpful? Let others know about it. Sharing helps me continue to create free Python resources.

TweetF  sharein  shareP  Pin

About Vishal

Image

I’m Vishal Hule, the Founder of PYnative.com. As a Python developer, I enjoy assisting students, developers, and learners. Follow me on Twitter.

Related Tutorial Topics:

C Programming Exercises

Python Exercises and Quizzes

Free coding exercises and quizzes cover Python basics, data structure, data analytics, and more.

  • 15+ Topic-specific Exercises and Quizzes
  • Each Exercise contains 10 questions
  • Each Quiz contains 12-15 MCQ
Exercises
Quizzes

Leave a Reply Cancel reply

your email address will NOT be published. all comments are moderated according to our comment policy.

Use <pre> tag for posting code. E.g. <pre> Your entire code </pre>

In: C Programming Exercises
TweetF  sharein  shareP  Pin

  C Exercises

  • All C Exercises
  • C Exercise for Beginners
  • C Variable and Data Type Exercise
  • C Loops Exercise
  • C Functions Exercise
  • C Arrays Exercise
  • C String Exercise
  • C Pointers Exercise
  • C File Handling Exercise
  • C Structures and Unions Exercise

All Coding Exercises

C Exercises Python Exercises

About PYnative

PYnative.com is for Python lovers. Here, You can get Tutorials, Exercises, and Quizzes to practice and improve your Python skills.

Explore Python

  • Learn Python
  • Python Basics
  • Python Databases
  • Python Exercises
  • Python Quizzes
  • Online Python Code Editor
  • Python Tricks

Follow Us

To get New Python Tutorials, Exercises, and Quizzes

  • Twitter
  • Facebook
  • Sitemap

Legal Stuff

  • About Us
  • Contact Us

We use cookies to improve your experience. While using PYnative, you agree to have read and accepted our:

  • Terms Of Use
  • Privacy Policy
  • Cookie Policy

Copyright © 2018–2025 pynative.com

Advertisement