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
- Base case – stops recursion.
- 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.,
qsortuses 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…