C Programming

Pointers and Functions

C Programming Exercise 6 of 7

Pointers and Functions

Master this concept with detailed explanations and interactive coding examples.

7 Questions
15 Minutes
0% Completed
#include <stdio.h> int main() { printf("Hello"); return 0; }

Interview Questions & Answers

Technical Interview

Pointers and Functions in C

If functions make your program structured, pointers make it powerful. Once you understand how pointers work with functions, you start thinking in terms of memory, not just variables.

This topic is extremely important for interviews, system programming, and real-world C development.


1. What is a Pointer?

A pointer is a variable that stores the address of another variable.

Basic Example


#include <stdio.h>

int main() {
    int a = 10;
    int *ptr = &a;

    printf("Value of a = %d\n", a);
    printf("Address of a = %p\n", &a);
    printf("Pointer value (address) = %p\n", ptr);
    printf("Value using pointer = %d\n", *ptr);

    return 0;
}

Explanation:

  • &a gives the address of variable a
  • ptr stores that address
  • *ptr gives the value stored at that address

2. Why Use Pointers with Functions?

Normally, C uses call by value. That means the function receives a copy of the variable.

If you want the function to modify the original variable, you must pass its address using pointers.


3. Call by Value vs Call by Reference

Call by Value (Original Value Not Changed)


#include <stdio.h>

void change(int x) {
    x = 50;
}

int main() {
    int a = 10;
    change(a);

    printf("Value of a = %d", a);
    return 0;
}

Output: 10


Call by Reference (Using Pointers)


#include <stdio.h>

void change(int *x) {
    *x = 50;
}

int main() {
    int a = 10;
    change(&a);

    printf("Value of a = %d", a);
    return 0;
}

Output: 50

Memory Explanation:

  • We pass the address of a
  • The function modifies the value stored at that address
  • The original variable changes

4. Swapping Two Numbers Using Pointers

This is a classic interview question.


#include <stdio.h>

void swap(int *a, int *b) {
    int temp = *a;
    *a = *b;
    *b = temp;
}

int main() {
    int x = 5, y = 10;

    swap(&x, &y);

    printf("x = %d, y = %d", x, y);

    return 0;
}

5. Passing Pointer to an Array

Arrays and pointers are closely related. The array name itself acts as a pointer to its first element.

Example


#include <stdio.h>

void printArray(int *arr, int size) {
    for(int i = 0; i < size; i++) {
        printf("%d ", *(arr + i));
    }
}

int main() {
    int arr[] = {10, 20, 30, 40};

    printArray(arr, 4);

    return 0;
}

6. Returning Pointer from a Function

You must be careful while returning pointers. Never return a pointer to a local variable.

Wrong Way


int* wrongFunction() {
    int a = 10;
    return &a;   // Dangerous (local variable)
}

This leads to undefined behavior.


Correct Way (Using Static Variable)


#include <stdio.h>

int* correctFunction() {
    static int a = 10;
    return &a;
}

int main() {
    int *ptr = correctFunction();
    printf("%d", *ptr);

    return 0;
}

7. Pointer to Pointer

A pointer that stores the address of another pointer.


#include <stdio.h>

int main() {
    int a = 5;
    int *p = &a;
    int **pp = &p;

    printf("Value of a = %d\n", **pp);

    return 0;
}

8. Function Pointer

A function pointer stores the address of a function.

Example


#include <stdio.h>

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

int main() {
    int (*ptr)(int, int);
    ptr = add;

    printf("Result = %d", ptr(3, 7));

    return 0;
}

9. Dynamic Memory Allocation with Functions

When you want flexible memory, you use malloc() from stdlib.


#include <stdio.h>
#include <stdlib.h>

int* createArray(int size) {
    int *arr = (int*) malloc(size * sizeof(int));

    for(int i = 0; i < size; i++) {
        arr[i] = i + 1;
    }

    return arr;
}

int main() {
    int size = 5;
    int *ptr = createArray(size);

    for(int i = 0; i < size; i++) {
        printf("%d ", ptr[i]);
    }

    free(ptr);

    return 0;
}

Important Interview Questions

  • What is the difference between pointer and array?
  • Why can’t we return address of local variable?
  • Explain double pointer with real example.
  • What is function pointer and where is it used?
  • Explain memory allocation using malloc and free.

Memory Understanding (Very Important)

  • Stack memory stores local variables
  • Heap memory is allocated using malloc
  • Static variables stay for entire program execution
  • Pointers help manipulate memory directly

0% read

Interactive Code Playground

Live Coding
main.c
1
$ Ready to run your code...
Tips:
  • Use Ctrl + Enter to run code
  • Include <stdio.h> for printf/scanf
  • main() function must return int
Sample Snippets:

Detailed Explanation

Pointers and Functions

Understanding Pointers and Functions in C

Pointers and functions together form the backbone of advanced C programming. Pointers can be used as function parameters (to modify variables), as return values, and even to create function pointers that allow dynamic function calls. This powerful combination enables callbacks, dynamic dispatch, and efficient memory management.

  • Pointer Parameters - Passing pointers to functions allows modification of original variables (simulated pass-by-reference).
  • Function Pointers - Variables that store the address of functions, enabling dynamic function calls and callbacks.
  • Pointer Return Types - Functions can return pointers to dynamically allocated memory, static data, or structures.
  • Pointer to Function as Parameter - Functions can accept function pointers as arguments, enabling flexible algorithms (e.g., qsort comparator).
  • Array of Function Pointers - Creating jump tables for implementing state machines or command dispatchers.
  • Null Pointer Checks - Always validate pointer parameters and return values to prevent crashes.

Key Points to Remember:

Pass by Reference Simulation

Pointers allow functions to modify caller's variables by passing addresses rather than values.

Function Pointer Syntax

return_type (*pointer_name)(parameter_types) - parentheses matter!

Dangling Pointers

Never return pointers to local variables - they cease to exist after function returns.

Callback Mechanism

Function pointers enable callbacks - passing one function to be called by another.

Expert Interview Tips:

When explaining pointer parameters, emphasize: 'The function receives a copy of the pointer, but both copies point to the same memory.'

2 min read 6,200 views Memory Model

Common mistake: Forgetting to dereference pointer parameters. *ptr = value modifies the pointed-to variable; ptr = value modifies the pointer itself.

2 min read 4,800 views Common Pitfall

Pro tip: Use typedef for function pointers to improve readability: typedef int (*Comparator)(const void*, const void*);

1 min read 3,900 views Code Clarity

Common Follow-up Questions:

How do you use pointers as function parameters to modify variables?
Basic

Pointers as function parameters allow functions to modify variables in the calling scope. This simulates pass-by-reference in C. The function receives the address of the variable, then uses the dereference operator * to access and modify the original value.

#include <stdio.h>

// Function that modifies variables via pointers
void swap(int *a, int *b) {
    int temp = *a;  // Dereference to get values
    *a = *b;
    *b = temp;
}

void increment(int *ptr) {
    (*ptr)++;  // Parentheses needed because ++ has higher precedence than *
}

int main() {
    int x = 10, y = 20;
    
    printf("Before swap: x = %d, y = %d\n", x, y);
    swap(&x, &y);  // Pass addresses
    printf("After swap: x = %d, y = %d\n", x, y);
    
    increment(&x);
    printf("After increment: x = %d\n", x);
    
    return 0;
}
πŸ’‘ The function receives a copy of the pointer, but both original and copy point to the same memory location.
What are function pointers? How do you declare and use them?
Intermediate

Function pointers store the address of a function, allowing functions to be called indirectly, passed as arguments, or stored in data structures.

Declaration syntax: return_type (*pointer_name)(parameter_types)

Key points:

  • Parentheses around *pointer_name are crucial
  • Can point to any function with matching signature
  • Use pointer name like a regular function to call
#include <stdio.h>

// Some functions with same signature
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 main() {
    // Declare function pointer
    int (*operation)(int, int);
    
    // Point to add function
    operation = &add;  // & is optional, function name decays to pointer
    printf("Add: %d\n", operation(10, 5));
    
    // Re-point to subtract
    operation = subtract;  // Without &
    printf("Subtract: %d\n", operation(10, 5));
    
    // Array of function pointers
    int (*operations[])(int, int) = {add, subtract, multiply};
    char *op_names[] = {"Add", "Subtract", "Multiply"};
    
    for(int i = 0; i < 3; i++) {
        printf("%s: %d\n", op_names[i], operations[i](10, 5));
    }
    
    return 0;
}
πŸ’‘ Function pointers enable runtime polymorphism in C - deciding which function to call based on runtime conditions.
How do you use function pointers as callback functions? Provide a practical example.
Advanced

Callbacks are functions passed as arguments to other functions, which then 'call back' at appropriate times. Common in event handling, sorting, and state machines.

Example: The C standard library's qsort function accepts a comparator callback.

#include <stdio.h>
#include <stdlib.h>

// Callback function type definition
typedef int (*Comparator)(const void*, const void*);

// Generic sort function that accepts callback
void sortAndPrint(int arr[], int size, Comparator cmp, const char* order) {
    qsort(arr, size, sizeof(int), cmp);
    
    printf("%s order: ", order);
    for(int i = 0; i < size; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");
}

// Comparator callbacks
int ascending(const void *a, const void *b) {
    return (*(int*)a - *(int*)b);
}

int descending(const void *a, const void *b) {
    return (*(int*)b - *(int*)a);
}

// Event simulation
typedef void (*EventHandler)(const char*);

void onButtonClick(EventHandler handler) {
    printf("Button clicked! ");
    handler("Button event");
}

void handleEvent(const char *event) {
    printf("Handling: %s\n", event);
}

int main() {
    int numbers[] = {42, 13, 7, 89, 23, 56};
    int size = sizeof(numbers)/sizeof(numbers[0]);
    
    // Use callbacks for sorting
    sortAndPrint(numbers, size, ascending, "Ascending");
    sortAndPrint(numbers, size, descending, "Descending");
    
    // Event callback
    onButtonClick(handleEvent);
    
    return 0;
}
πŸ’‘ Callbacks decouple the 'what' from the 'when' - the called function doesn't need to know details of what will be called.
What are the risks of returning pointers from functions? How do you avoid them?
Advanced

Risks of returning pointers:

  • Dangling pointers: Returning pointer to local variable (invalid after function returns)
  • Memory leaks: Returning pointer to dynamically allocated memory without clear ownership
  • Null pointers: Returning NULL without proper error handling
  • Shared data issues: Returning pointer to static data (not thread-safe)

Best practices:

  • Never return pointers to local variables
  • Document ownership: who is responsible for freeing?
  • Check for NULL before using returned pointers
  • Consider returning by value for small structures
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// WRONG: Returns pointer to local variable
int* badFunction() {
    int local = 42;
    return &local;  // Dangling pointer!
}

// RIGHT: Returns pointer to dynamic memory
int* createArray(int size) {
    int *arr = (int*)malloc(size * sizeof(int));
    if(arr == NULL) {
        return NULL;  // Indicate failure
    }
    for(int i = 0; i < size; i++) {
        arr[i] = i * 10;
    }
    return arr;  // Caller must free()
}

// RIGHT: Returns pointer to static data (but not thread-safe)
char* getErrorMessage(int code) {
    static char msg[100];  // Static duration
    sprintf(msg, "Error code: %d", code);
    return msg;  // Safe, but subsequent calls overwrite
}

// RIGHT: Caller provides buffer
void getString(char *buffer, int size) {
    strncpy(buffer, "Hello World", size - 1);
    buffer[size - 1] = '\0';
}

int main() {
    // int *p = badFunction();  // Never use this!
    
    int *arr = createArray(5);
    if(arr != NULL) {
        for(int i = 0; i < 5; i++) {
            printf("%d ", arr[i]);
        }
        printf("\n");
        free(arr);  // MUST free
    }
    
    char *msg = getErrorMessage(404);
    printf("%s\n", msg);
    
    char buffer[50];
    getString(buffer, 50);
    printf("%s\n", buffer);
    
    return 0;
}
πŸ’‘ Three safe options: dynamic allocation (with clear ownership), static data (with warnings), or caller-provided buffer.
Explain the difference between passing a pointer and passing a pointer to a pointer as a function parameter.
Advanced

Pointer parameter (int *ptr): Allows modification of the pointed-to data, but not the pointer itself.

Pointer to pointer parameter (int **ptr): Allows modification of the pointer itself (what it points to). Used when:

  • Function needs to allocate memory and return it via parameter
  • Function needs to modify a pointer variable in caller's scope
  • Working with arrays of pointers
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// Can modify data, but not the pointer
void modifyData(int *ptr) {
    *ptr = 100;  // OK - modifies pointed-to data
    // ptr = NULL;  // Only affects local copy, not caller's pointer
}

// Can modify the pointer itself
void modifyPointer(int **ptr_ptr) {
    static int new_value = 999;
    *ptr_ptr = &new_value;  // Changes what caller's pointer points to
}

// Allocate memory and return via pointer-to-pointer
void createString(char **str_ptr, int size) {
    *str_ptr = (char*)malloc(size * sizeof(char));
    if(*str_ptr != NULL) {
        strcpy(*str_ptr, "Hello");
    }
}

int main() {
    int x = 42;
    int *p = &x;
    
    printf("Before modifyData: *p = %d\n", *p);
    modifyData(p);
    printf("After modifyData: *p = %d\n", *p);
    
    printf("Before modifyPointer: p points to %d\n", *p);
    modifyPointer(&p);  // Pass address of pointer
    printf("After modifyPointer: p points to %d\n", *p);
    
    char *str = NULL;
    createString(&str, 20);
    if(str != NULL) {
        printf("String: %s\n", str);
        free(str);
    }
    
    return 0;
}
πŸ’‘ Use int** when the function needs to change where the pointer points (like allocating memory or changing to a different variable).
How can you create an array of function pointers? Provide a practical use case.
Advanced

Array of function pointers (jump table) allows selecting and calling functions based on indices, useful for:

  • State machines
  • Command dispatchers (menu systems)
  • Operation tables (calculators)
  • Plugin architectures
#include <stdio.h>

// Calculator operations
typedef double (*MathOp)(double, double);

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) { return b != 0 ? a / b : 0; }

// Menu system
typedef void (*MenuFunction)(void);

void option1() { printf("You selected Option 1\n"); }
void option2() { printf("You selected Option 2\n"); }
void option3() { printf("You selected Option 3\n"); }
void option4() { printf("You selected Option 4\n"); }

int main() {
    // Calculator jump table
    MathOp operations[] = {add, subtract, multiply, divide};
    char *op_symbols[] = {"+", "-", "*", "/"};
    
    double a = 20, b = 5;
    for(int i = 0; i < 4; i++) {
        printf("%.1f %s %.1f = %.1f\n", a, op_symbols[i], b, operations[i](a, b));
    }
    
    // Menu system using function pointer array
    MenuFunction menu[] = {option1, option2, option3, option4};
    
    printf("\nMenu System:\n");
    for(int choice = 0; choice < 4; choice++) {
        printf("\nExecuting choice %d:\n", choice + 1);
        menu[choice]();
    }
    
    // State machine example
    printf("\nState Machine:\n");
    typedef enum {STATE_IDLE, STATE_RUN, STATE_PAUSE, STATE_STOP} State;
    
    void idle() { printf("Idle state\n"); }
    void run() { printf("Running\n"); }
    void pause() { printf("Paused\n"); }
    void stop() { printf("Stopped\n"); }
    
    void (*state_handlers[])(void) = {idle, run, pause, stop};
    
    State current_state = STATE_RUN;
    state_handlers[current_state]();
    
    current_state = STATE_PAUSE;
    state_handlers[current_state]();
    
    return 0;
}
πŸ’‘ Function pointer arrays eliminate lengthy switch/case statements and make code more maintainable and extensible.
How do const pointers work with function parameters? Explain different combinations.
Intermediate

Four combinations of const with pointer parameters:

  1. void func(const int *ptr) - Pointer to const int: Cannot modify pointed-to data
  2. void func(int *const ptr) - Const pointer to int: Cannot modify pointer itself, but can modify data
  3. void func(const int *const ptr) - Const pointer to const int: Cannot modify either
  4. void func(int *ptr) - No const: Can modify both
#include <stdio.h>

// 1. Pointer to const data (most common for input)
void readOnly(const int *ptr) {
    // *ptr = 100;  // ERROR: Can't modify data
    int value = *ptr;  // OK: Can read
    printf("Read-only: %d\n", value);
    // ptr = NULL;  // OK: Can modify pointer
}

// 2. Const pointer to data
void fixedPointer(int *const ptr) {
    *ptr = 200;  // OK: Can modify data
    // ptr = NULL;  // ERROR: Can't modify pointer
}

// 3. Const pointer to const data
void completelyFixed(const int *const ptr) {
    // *ptr = 300;  // ERROR: Can't modify data
    // ptr = NULL;  // ERROR: Can't modify pointer
    printf("Completely fixed: %d\n", *ptr);
}

// 4. Regular pointer
void normalPointer(int *ptr) {
    *ptr = 400;  // OK
    ptr = NULL;  // OK (local copy only)
}

int main() {
    int x = 10;
    
    readOnly(&x);
    printf("After readOnly: x = %d\n", x);
    
    fixedPointer(&x);
    printf("After fixedPointer: x = %d\n", x);
    
    completelyFixed(&x);
    
    normalPointer(&x);
    printf("After normalPointer: x = %d\n", x);
    
    return 0;
}
πŸ’‘ Read const declarations from right to left: const int *ptr = 'ptr is a pointer to const int'