CSC115 C Programming

C ProgrammingUnit 78 min read

Unit 7: Functions – Definitions, Parameters, Recursion & Function Pointers

Unit 7 of C Programming: a comprehensive guide to function concepts, including declaration, definition, parameter passing, recursion, function pointers, and the distinction between library and user‑defined functions.

Key points

  • Functions encapsulate reusable code blocks, defined by a return type, name, and parameter list.
  • Parameter passing in C is by value; to modify arguments, pointers (call‑by‑reference) are used.
  • Recursion allows a function to call itself, requiring a base case to terminate.
  • Function pointers enable dynamic dispatch and callback mechanisms.
  • Library functions are pre‑compiled and linked, whereas user‑defined functions are written and compiled by the programmer.

Function Basics

In C, a function is a named block of code that performs a specific task and may return a value. The general syntax is:

return_type function_name(parameter_list) {
    /* body */
}

Functions promote modularity, readability, and maintainability. They also enable abstraction: callers need not know the internal workings, only the interface.

Key Terminology

Term Definition
Return type Data type of the value the function returns (int, void, struct, etc.).
Function name Identifier used to call the function.
Parameter list Zero or more typed variables that receive arguments.
Prototype Declaration of a function’s signature, placed before its first use.
Definition Full implementation of the function.
Linkage Visibility of a function across translation units (extern, static).
Scope The region of the program where a function name is visible.

Function Declaration and Prototype

A prototype informs the compiler about a function’s return type and parameter types before its actual definition. It allows type checking of arguments at compile time.

int add(int a, int b);   /* Prototype */

If a prototype is omitted, the compiler assumes an implicit declaration (old C), which can lead to errors. Modern C (C99 onward) requires prototypes for all functions.

Placement

  • Header files (.h) typically contain prototypes for functions that are shared across multiple source files.
  • Source files (.c) contain the actual definitions.

Function Definition

A function definition provides the body. Example:

int add(int a, int b) {
    return a + b;
}

The compiler checks that the return type matches the prototype. If the function returns void, no value is returned.

Example: Swapping Two Integers Using Call‑by‑Reference

void swap(int *x, int *y) {
    int temp = *x;
    *x = *y;
    *y = temp;
}

Call site:

int main(void) {
    int a = 5, b = 10;
    swap(&a, &b);   /* Pass addresses */
    printf("%d %d\n", a, b);  /* 10 5 */
}

Return Types

Return Type Usage Example
void No value returned void printHello(void);
Primitive (int, float, etc.) Simple value int factorial(int n);
struct Return a composite value struct Point {int x; int y;};
Pointer Return address of data int *find(int *arr, int n, int key);
char * Return string char *getName(void);

Returning a Pointer to a Static Variable

int *counter(void) {
    static int count = 0;   /* Static: persists across calls */
    return &count;          /* Safe to return address */
}

Returning a pointer to a local (automatic) variable is unsafe because the variable’s lifetime ends when the function returns.

Parameter Passing

C uses pass‑by‑value: the function receives copies of the arguments. To modify the caller’s variables, pointers are used.

Pass‑by‑Value vs Pass‑by‑Reference

Feature Pass‑by‑Value Pass‑by‑Reference (Pointer)
Syntax void f(int a); void f(int *a);
Caller’s variable Unchanged Modified
Safety No aliasing Potential aliasing, risk of dangling pointers
Typical use Simple data Large structs, arrays, or when modification is needed
Overhead None Indirection cost

Example Trace: Recursive Factorial

int factorial(int n) {
    if (n <= 1) return 1;          /* Base case */
    return n * factorial(n - 1);   /* Recursive call */
}

Trace for factorial(4):

factorial(4)
  -> 4 * factorial(3)
        -> 3 * factorial(2)
              -> 2 * factorial(1)
                    -> 1   (base case)
              -> 2 * 1 = 2
        -> 3 * 2 = 6
  -> 4 * 6 = 24

Result: 24.

Recursion

Recursion is a technique where a function calls itself. It is powerful for problems naturally defined in terms of smaller sub‑problems (e.g., factorial, Fibonacci, tree traversal).

Requirements

  1. Base case – stops recursion.
  2. Recursive case – reduces the problem size.

Common Pitfalls

  • Infinite recursion: missing or incorrect base case.
  • Stack overflow: too deep recursion.
  • Redundant calculations: e.g., naive Fibonacci leads to exponential time.

Optimized Fibonacci Using Memoization

int fib(int n, int *memo) {
    if (n <= 1) return n;
    if (memo[n] != -1) return memo[n];
    memo[n] = fib(n-1, memo) + fib(n-2, memo);
    return memo[n];
}

Function Pointers

A function pointer stores the address of a function and can be invoked indirectly. Syntax:

return_type (*ptr_name)(parameter_types);

Declaration and Assignment

int add(int a, int b) { return a + b; }
int (*func_ptr)(int, int) = add;   /* Assign address of add */

Invocation

int result = func_ptr(3, 4);   /* Calls add(3,4) */

Use Cases

  • Callbacks: e.g., qsort uses a comparison function pointer.
  • Dynamic dispatch: selecting behavior at runtime.
  • Event handling: GUI libraries pass function pointers as handlers.

Example: Sorting with qsort

int cmp_int(const void *a, const void *b) {
    int ia = *(const int *)a;
    int ib = *(const int *)b;
    return (ia > ib) - (ia < ib);
}

int main(void) {
    int arr[] = {5, 2, 9, 1};
    qsort(arr, 4, sizeof(int), cmp_int);
}

Static and External Functions

Linkage Visibility Example
static Internal (only within the translation unit) static void helper(void);
extern External (visible across translation units) extern int globalVar;

Static functions prevent name clashes and encapsulate helper routines.

Library vs User‑Defined Functions

Aspect Library Function User‑Defined Function
Source Pre‑compiled, part of standard or third‑party libraries Written by the programmer
Availability Requires linking against library Compiled with the program
Modification Not modifiable (unless re‑implementing) Fully customizable
Examples printf, malloc, qsort int add(int a, int b)

Advantages of Library Functions

  • Optimized: often highly efficient.
  • Well‑tested: reliability and portability.

Disadvantages

  • Opaque: internal workings hidden.
  • Limited flexibility: may not fit niche requirements.

Common Pitfalls and Best Practices

Pitfall Explanation Remedy
Returning address of local variable Local variable goes out of scope Use static or allocate dynamically
Uninitialized pointers Leads to segmentation faults Initialize to NULL or valid address
Forgetting return in non‑void function Undefined behavior Ensure every path returns a value
Recursive depth too large Stack overflow Use iterative approach or tail recursion
Mixing int and float in arithmetic Implicit conversion may lose precision Explicit casting or use consistent types

Summary Table

Feature Description Example
Function prototype Declares signature before use int add(int, int);
Function definition Provides implementation int add(int a, int b) { return a + b; }
Return by value Copies result return a + b;
Return by pointer Returns address return &count;
Pass by value Copies arguments void f(int a);
Pass by reference Uses pointers void f(int *a);
Recursion Function calls itself int fact(int n) { if(n<=1)return1; return n*fact(n-1); }
Function pointer Stores function address int (*fp)(int,int)=add;
Static function Internal linkage static void helper(void);
Library function Pre‑compiled printf, malloc
User‑defined function Written by programmer int add(int a, int b);

Exam tip

  • Understand the difference between prototype, definition, and declaration.
  • Be able to write a function that swaps two integers using pointers.
  • Trace recursive calls and identify base cases.
  • Explain function pointers and give a simple callback example.
  • Distinguish library functions from user‑defined ones and discuss advantages.
  • Practice writing prototypes in header files and definitions in source files.

Focus on clarity and correct syntax; exam questions often test your ability to write concise, error‑free code snippets.

Based on the TU BSc CSIT syllabus for C Programming (CSC115), unit 7.

Discussion

Loading…