CoreTechED

Loading

Function Pointer in C: Complete Guide with Examples

Learn C function pointers: declaration, initialization, callbacks, function tables, and structure member functions. Master dynamic function invocation.

Function Pointer in C: Complete Guide with Examples

Function Pointer in C: A Comprehensive Tutorial

A function pointer is a pointer that stores the address of a function, allowing the function to be called indirectly through the pointer. Function pointers provide flexibility in invoking functions dynamically, enabling callbacks, event handling, and dynamic function selection.

This tutorial provides an in-depth exploration of function pointers in C: declaration, initialization, properties, applications (callbacks, structure member functions, arrays of function pointers), common pitfalls, and best practices.


Conceptual Introduction & Fundamentals

What is a Function Pointer?

A function pointer is a variable that stores the memory address of a function. Just as data pointers store addresses of variables, function pointers store addresses of functions in the code segment.

Example:

c

int add(int a, int b) { return a + b; } int (*fptr)(int, int); // Function pointer declaration fptr = &add; // Store address of add int result = fptr(10, 5); // Call through pointer (result = 15)

Why Use Function Pointers?

  • Callbacks: Pass functions as arguments to other functions.
  • Dynamic function selection: Choose which function to call at runtime.
  • Function tables: Arrays of functions for menu-driven programs.
  • Polymorphic behavior: Emulate object-oriented programming in C.
  • Event handling: Register handlers for different events.
  • Sorting: Pass comparison functions to generic sort routines.

Function Pointer vs. Data Pointer

AspectData PointerFunction Pointer
Points toVariables in memoryFunctions in code segment
DereferenceAccess data valueCall function
ArithmeticAllowed (with restrictions)Not allowed
ComparisonAllowed (same type)Allowed (equality/inequality)
SizeArchitecture-dependentArchitecture-dependent

Syntax & Core Mechanics

Declaration

Syntax:

c

return_type (*pointer_name)(parameter_types);

Examples:

c

int (*fptr)(int, int); // Pointer to function taking 2 ints, returning int void (*vptr)(void); // Pointer to function taking nothing, returning void double (*dptr)(double, double); // Pointer to function taking 2 doubles, returning double

Important: Parentheses around *pointer_name are necessary. Without them:

c

int *fptr(int, int); // Function that returns int* (not a function pointer)

Initialization

c

#include <stdio.h> int add(int a, int b) { return a + b; } int main(void) { /* Method 1: Using address-of operator */ int (*fptr1)(int, int) = &add; /* Method 2: Without address-of operator (function name decays) */ int (*fptr2)(int, int) = add; printf("fptr1(10, 5) = %d\n", fptr1(10, 5)); printf("fptr2(10, 5) = %d\n", fptr2(10, 5)); return 0; }

Expected output:

text

fptr1(10, 5) = 15 fptr2(10, 5) = 15

Calling Through Function Pointer

There are multiple equivalent syntaxes:

c

#include <stdio.h> int add(int a, int b) { return a + b; } int main(void) { int (*fptr)(int, int) = add; /* Method 1: Direct call */ printf("%d\n", fptr(10, 5)); /* Method 2: Explicit dereference */ printf("%d\n", (*fptr)(10, 5)); /* Method 3: Using pointer notation */ printf("%d\n", (*(&fptr))(10, 5)); return 0; }

Expected output:

text

15 15 15

Properties of Function Pointers

  1. Point to code segment (not data).
  2. Must match exact function signature.
  3. Can point to different functions with matching signatures.
  4. Cannot perform arithmetic operations.
  5. Support array-like functionality for function tables.

c

#include <stdio.h> int add(int a, int b) { return a + b; } int sub(int a, int b) { return a - b; } int main(void) { int (*fptr)(int, int); fptr = add; printf("add: %d\n", fptr(10, 5)); fptr = sub; printf("sub: %d\n", fptr(10, 5)); /* fptr++; // Error: arithmetic on function pointer not allowed */ return 0; }

Expected output:

text

add: 15 sub: 5

Applications of Function Pointers

1. Callback Functions

Passing functions as arguments to other functions.

c

#include <stdio.h> 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; } void calculate(int a, int b, int (*operation)(int, int)) { printf("Result: %d\n", operation(a, b)); } int main(void) { calculate(10, 5, add); calculate(10, 5, subtract); calculate(10, 5, multiply); return 0; }

Expected output:

text

Result: 15 Result: 5 Result: 50

2. Emulating Member Functions in Structures

c

#include <stdio.h> typedef struct Rectangle { int width; int height; void (*set)(struct Rectangle*, int, int); int (*area)(struct Rectangle*); void (*display)(struct Rectangle*); } Rectangle; void setDimensions(Rectangle *r, int w, int h) { r->width = w; r->height = h; } int calculateArea(Rectangle *r) { return r->width * r->height; } void displayRectangle(Rectangle *r) { printf("Width: %d, Height: %d\n", r->width, r->height); } void initRectangle(Rectangle *r) { r->width = 0; r->height = 0; r->set = setDimensions; r->area = calculateArea; r->display = displayRectangle; } int main(void) { Rectangle rect; initRectangle(&rect); rect.set(&rect, 10, 5); rect.display(&rect); printf("Area: %d\n", rect.area(&rect)); return 0; }

Expected output:

text

Width: 10, Height: 5 Area: 50

3. Array of Function Pointers

c

#include <stdio.h> 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; } int divide(int a, int b) { if (b != 0) return a / b; return -1; } int main(void) { int (*operations[])(int, int) = {add, subtract, multiply, divide}; int x = 10, y = 5; printf("Sum: %d\n", operations[0](x, y)); printf("Difference: %d\n", operations[1](x, y)); printf("Product: %d\n", operations[2](x, y)); printf("Quotient: %d\n", operations[3](x, y)); return 0; }

Expected output:

text

Sum: 15 Difference: 5 Product: 50 Quotient: 2

Full Practical Code Example with Explanation

This program demonstrates function pointers in a realistic scenario: a menu-driven calculator with callback functions.

Program: Function Pointer Calculator

c

/** * function_pointer_calculator.c * Demonstrates function pointers with callbacks, function tables, * and dynamic function selection. */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #define MAX_LINE_LEN 100 /* Function prototypes for arithmetic operations */ double add(double a, double b) { return a + b; } double subtract(double a, double b) { return a - b; } double multiply(double a, double b) { return a * b; } double divide(double a, double b) { if (b == 0.0) { fprintf(stderr, "Error: Division by zero\n"); return 0.0; } return a / b; } /* Function pointer type definition */ typedef double (*Operation)(double, double); /* Function that uses a callback */ void performOperation(double a, double b, Operation op, const char *opName) { double result = op(a, b); printf("%.2f %s %.2f = %.2f\n", a, opName, b, result); } /* Function prototypes for menu */ void displayMenu(void); double readNumber(const char *prompt); int main(void) { /* Array of function pointers (function table) */ Operation operations[] = {add, subtract, multiply, divide}; const char *opNames[] = {"+", "-", "*", "/"}; int choice; char buffer[MAX_LINE_LEN]; double num1, num2; printf("=== Function Pointer Calculator ===\n"); while (1) { displayMenu(); if (fgets(buffer, sizeof(buffer), stdin) == NULL) { fprintf(stderr, "Error reading choice.\n"); return EXIT_FAILURE; } choice = atoi(buffer); switch (choice) { case 1: case 2: case 3: case 4: { num1 = readNumber("Enter first number: "); num2 = readNumber("Enter second number: "); /* Call through function pointer */ performOperation(num1, num2, operations[choice - 1], opNames[choice - 1]); break; } case 5: { /* Demonstrate callback directly */ printf("\nCallback demonstration:\n"); num1 = readNumber("Enter first number: "); num2 = readNumber("Enter second number: "); printf("Using add callback: "); performOperation(num1, num2, add, "+"); printf("Using multiply callback: "); performOperation(num1, num2, multiply, "*"); break; } case 6: printf("Exiting.\n"); return EXIT_SUCCESS; default: printf("Invalid choice. Please try again.\n"); break; } } return EXIT_SUCCESS; } /* Displays the menu */ void displayMenu(void) { printf("\n--- Calculator Menu ---\n"); printf("1. Addition\n"); printf("2. Subtraction\n"); printf("3. Multiplication\n"); printf("4. Division\n"); printf("5. Callback Demo\n"); printf("6. Exit\n"); printf("Enter choice: "); fflush(stdout); } /* Reads a number safely */ double readNumber(const char *prompt) { char buffer[MAX_LINE_LEN]; char *endPtr; double value; while (1) { printf("%s", prompt); fflush(stdout); if (fgets(buffer, sizeof(buffer), stdin) == NULL) { fprintf(stderr, "Error reading input.\n"); exit(EXIT_FAILURE); } errno = 0; value = strtod(buffer, &endPtr); if (endPtr == buffer || (*endPtr != '\n' && *endPtr != '\0')) { printf("Invalid number. Please try again.\n"); continue; } if (errno == ERANGE) { printf("Number out of range. Please try again.\n"); continue; } return value; } }

Explanation of Function Pointer Usage

  1. Function pointer type: typedef double (*Operation)(double, double);
  2. Function table: Array of function pointers operations[].
  3. Callback: performOperation accepts a function pointer.
  4. Dynamic selection: Menu choice selects which function to call.
  5. Type safety: Function pointers ensure matching signatures.

Expected Output (sample run)

text

=== Function Pointer Calculator === --- Calculator Menu --- 1. Addition 2. Subtraction 3. Multiplication 4. Division 5. Callback Demo 6. Exit Enter choice: 1 Enter first number: 10 Enter second number: 5 10.00 + 5.00 = 15.00 --- Calculator Menu --- ... Enter choice: 3 Enter first number: 10 Enter second number: 5 10.00 * 5.00 = 50.00 --- Calculator Menu --- ... Enter choice: 5 Callback demonstration: Enter first number: 8 Enter second number: 4 Using add callback: 8.00 + 4.00 = 12.00 Using multiply callback: 8.00 * 4.00 = 32.00 --- Calculator Menu --- ... Enter choice: 6 Exiting.

Common Pitfalls, Edge Cases & Memory Considerations

1. Type Mismatch

Function pointer type must exactly match the function signature.

c

int add(int a, int b) { return a + b; } int (*fptr)(double, double) = add; // Warning: incompatible type

Solution: Ensure return type and parameter types match exactly.

2. Forgetting Parentheses

Omitting parentheses changes the meaning.

c

int *fptr(int, int); // Function returning int* (not function pointer) int (*fptr)(int, int); // Correct function pointer

Solution: Always use parentheses around *pointer_name.

3. Calling NULL Function Pointer

Calling a NULL function pointer causes a crash.

c

int (*fptr)(int, int) = NULL; fptr(10, 5); // BUG: NULL function pointer call

Solution: Check if (fptr != NULL) before calling.

4. Arithmetic on Function Pointers

Function pointers do not support arithmetic.

c

fptr++; // Error: arithmetic on function pointer

Solution: Use arrays of function pointers for sequential access.

5. Casting Function Pointers

Casting function pointers to different types can cause undefined behavior when called.

c

int add(int a, int b) { return a + b; } void (*vptr)(void) = (void (*)(void))add; vptr(); // BUG: calling with wrong signature

Solution: Avoid casting function pointers to incompatible types.

6. Using typedef for Readability

Complex function pointer declarations can be hard to read.

c

int (*(*complex)(int))(double); // Hard to understand

Solution: Use typedef to simplify:

c

typedef int (*FuncPtr)(double); FuncPtr (*complex)(int);

7. Returning Function Pointers

Functions can return function pointers, but syntax is complex.

c

int (*getOperation(char op))(int, int) { switch (op) { case '+': return add; case '-': return subtract; default: return NULL; } }

Solution: Use typedef for clarity.

8. Comparing Function Pointers

Function pointers can be compared for equality/inequality, but not for ordering.

c

if (fptr1 == fptr2) { ... } // OK if (fptr1 < fptr2) { ... } // Not allowed in standard C

Solution: Only use == and != for function pointer comparison.

9. Function Pointer and Data Pointer Conversion

Converting between function pointers and data pointers is not guaranteed by the C standard.

c

void *dataPtr = (void*)fptr; // Not portable

Solution: Avoid converting between function and data pointers.

10. Memory Considerations

  • Function pointers occupy memory (8 bytes on 64-bit).
  • Arrays of function pointers consume n × sizeof(function pointer) bytes.
  • Function pointers do not allocate dynamic memory.
  • Function pointer tables are stored in the data segment.

Summary & Best Practices

Function pointers are a powerful feature that enables dynamic function invocation, callbacks, and polymorphic behavior in C. They require careful type management to avoid undefined behavior.

Key Takeaways

  • Function pointers store addresses of functions.
  • Syntax: return_type (*name)(parameter_types);
  • Must match function signature exactly.
  • Cannot perform arithmetic.
  • Used for callbacks, function tables, and structure member functions.
  • Always check for NULL before calling.

Best Practices

  1. Use typedef for function pointer types to improve readability.
  2. Always check for NULL before calling through a function pointer.
  3. Match function signatures exactly to avoid type mismatches.
  4. Use function pointers for callbacks to enable flexible function selection.
  5. Use arrays of function pointers for menu-driven programs.
  6. Document function pointer parameters with comments.
  7. Avoid casting function pointers to incompatible types.
  8. Use const where appropriate for function pointer parameters.
  9. Test edge cases: NULL pointers, wrong signatures, empty function tables.
  10. Enable compiler warnings to catch function pointer issues.

By mastering function pointers, you’ll write flexible, dynamic C programs that implement callbacks, event handling, and polymorphic behavior efficiently.

Leave a Reply

Your email address will not be published. Required fields are marked *