Learning Notes on Callback Functions in C Language

Today, I studied callback functions and recorded some of my thoughts based on articles from experts online. I will continue to write about the subsequent topics.

What is a callback function?

A callback function is a function that is called through a function pointer. If you pass the pointer (address) of a function as a parameter to another function, when this pointer is used to call the function it points to, we say this is a callback function.

A callback function is not called directly by the implementation of that function, but is called by another party when a specific event or condition occurs, used to respond to that event or condition.

This explanation is a bit long and convoluted. Below, I will illustrate what a callback is with a diagram:

Learning Notes on Callback Functions in C Language

Assuming we want to use a sorting function to sort an array, in the main program(Main program), we first select a library sorting function(Library function) from the library. However, there are many sorting algorithms, such as bubble sort, selection sort, quick sort, and merge sort. At the same time, we may also need to sort special objects, such as specific structures. The library function will choose a sorting algorithm based on our needs and then call the function that implements that algorithm to complete the sorting task. The called sorting function is the callback function(Callback function).

Combining this diagram with the previous explanation of callback functions, we can find that the key point to implement a callback function is to pass the function pointer to a function(the library function in the diagram above), and then this function can call the callback function through this pointer. Note that callback functions are not unique to the C language; almost any language has callback functions. In the C language, we implement callback functions using function pointers. So, what is a function pointer? Don’t worry, let’s first look at what a function pointer is.

What is a function pointer?

A function pointer is also a type of pointer, but it points to a function instead of an integer or character. In C, each function is stored in memory after compilation, and each function has an entry address. Based on this address, we can access and use this function. A function pointer calls this function by pointing to its entry.

Using function pointers

Defining function pointers

Although function pointers are also pointers, their definition looks quite different from other pointers. Let’s see how they are defined:

/* Method 1 */void (*p_func)(int, int, float) = NULL;
/* Method 2 */typedef void (*tp_func)(int, int, float);tp_func p_func = NULL;

Both methods define a function pointer that points to a function with a return type of void and parameters of (int, int, float).

The second method is to make the function pointer easier to understand, especially in complex environments; for general function pointers, the first method is sufficient.

The code snippet shows two ways to define function pointers in C:

Method 1: Direct definition

void (*p_func)(int, int, float) = NULL;
  • Directly defines a function pointer variable named<span><span>p_func</span></span> that can point to a function returning

  • This pointer can point to a function that returns<span><span>void</span></span> and accepts three parameters: two<span><span>int</span></span> and one<span><span>float</span></span>

  • Initialized to<span><span>NULL</span></span> indicates that it currently does not point to any function

Method 2: Using typedef to define a type alias

typedef void (*tp_func)(int, int, float);tp_func p_func = NULL;
  1. First, use<span><span>typedef</span></span> to define a new type<span><span>tp_func</span></span>, which represents the pointer type of “a function returning void and accepting (int, int, float) parameters”

  2. Then declare a variable using this new type<span><span>p_func</span></span> and initialize it to<span><span>NULL</span></span>

Assigning values to function pointers

After defining the function pointer, we need to assign a value to it. We have two ways to assign values to function pointers:

void (*p_func)(int, int, float) = NULL;p_func = &func1;p_func = func2;

Comparison of the two methods

  • Method 1 is suitable for one-time use function pointers

  • Method 2 is more suitable for situations where the same type of function pointer needs to be declared multiple times, improving code readability and maintainability

Both methods ultimately create the same function pointer variable<span><span>p_func</span></span>, which is currently initialized to a null pointer.

The above two methods are both valid. For the second method, the compiler will implicitly convertfunc_2 from void ()(int, int, float) type to void (*)(int, int, float) type, so both methods are acceptable.

Using function pointers to call functions

Since function pointers are also pointers, we can use the usual method with* to call functions.

Like assigning values to function pointers, we can also use two methods:

/* Method 1 */int val1 = p_func(1,2,3.0);
/* Method 2 */int val2 = (*p_func)(1,2,3.0);

Method 1 is the same as directly calling a function, while Method 2 uses* to dereference the function pointer, thus calling the function.

Both calling methods are functionally equivalent, and modern C code tends to prefer the concise syntax of Method 1

Passing function pointers as parameters to functions

Like regular pointers, we can pass function pointers as parameters to functions. Let’s see how to implement passing function pointers:

/* func3 takes function pointer p_func as its parameter */void func3(int a, int b, float c, void (*p_func)(int, int, float)){    (*p_func)(a, b, c);}
/* func4 calls function func3 */void func4(){    func3(1, 2, 3.0, func_1);    /* or func3(1, 2, 3.0, &func_1); */}

This code snippet demonstrates the typical usage of passing function pointers as parameters, which is a core technique for implementing callback mechanisms in C.

Function pointers as return types

With the foundation laid above, writing a function that returns a function pointer should not be difficult. The following example is a function with a return type of a function pointer:

void (* func5(int, int, float ))(int, int){    ...}

Here, func5 takes (int, int, float) as parameters, and its return type is void (*)(int, int) .

Function pointer arrays

Before explaining callback functions, let’s introduce function pointer arrays.

Since function pointers are also pointers, we can use arrays to store function pointers.

Now, let’s look at an example of a function pointer array:

/* Method 1 */void (*func_array_1[5])(int, int, float);
/* Method 2 */typedef void (*p_func_array)(int, int, float);p_func_array func_array_2[5];

Both methods can be used to define a function pointer array, defining an array with 5 elements of typevoid (*)(int, int, float) .

Method 1: Direct definition

/* Method 1 */void (*func_array_1[5])(int, int, float);

Analysis:

  1. <span><span>func_array_1[5]</span></span> defines an array containing 5 elements

  2. <span><span>*func_array_1[5]</span></span> indicates that each element of the array is a pointer

  3. <span><span>void (*func_array_1[5])(int, int, float)</span></span> indicates that these pointers point to functions of type:

  • Return type:<span><span>void</span></span>

  • Parameter list:<span><span>(int, int, float)</span></span>

Interpretation order (from inside out):

  1. <span><span>func_array_1</span></span> is an array

  2. Array elements are pointers <span><span>*</span></span>

  3. Pointers point to functions <span><span>(int, int, float)</span></span>

  4. Functions return <span><span>void</span></span>

Method 2: Using typedef to define

typedef void (*p_func_array)(int, int, float);p_func_array func_array_2[5];

Step-by-step analysis:

  1. <span><span>typedef</span></span> defines a new type <span><span>p_func_array</span></span>:

  • This is a function pointer type

  • Pointing to a function signature of <span><span>void (int, int, float)</span></span>

  • Then declare an array using this type:

    • <span><span>p_func_array func_array_2[5]</span></span> indicates:

    • <span><span>func_array_2</span></span> is an array containing 5 elements of type <span><span>p_func_array</span></span>

    Comparison of the two methods

    Feature Method 1 (Direct Definition) Method 2 (Typedef Definition)
    Readability Lower, complex syntax Higher, clear type definition
    Reusability Limited to current definition Type can be reused in multiple places
    Maintainability Modifications require changes in multiple places Only need to modify typedef
    Applicable Scenarios One-time use Types that need to be used multiple times

    Summary

    Why did I start learning about callback functions?

    I currently have two reasons:

    1. Asynchronous operations:

    In asynchronous programming, callback functions are the main means of handling the results of time-consuming operations (such as turning on a light, current protection, voltage protection, serial printing, etc.). Since these operations may take a long time to complete, the program does not block waiting for them to finish but continues executing other tasks. When the asynchronous operation is complete, it calls the callback function to handle the result.

    Here is an example:

    setTimeout(() =&gt; {    console.log('This is a callback function, called after 3 seconds');}, 3000);

    2. Implementing non-blocking I/O:

    In programs that need to handle a large number of input/output (I/O) operations, using callback functions can avoid performance issues caused by blocking I/O operations. Through asynchronous I/O and callback functions, the program can continue executing other tasks while waiting for I/O operations to complete.

    Callback functions are important in programming because they provide a flexible, efficient, and scalable mechanism to handle asynchronous operations, event responses, code decoupling, and modularization.

    By understanding and using callback functions, programmers can write more robust, maintainable, and scalable code.

    In C language, similar callback function functionality can also be implemented, but since C language does not natively support asynchronous programming and closures, the implementation of callback functions is slightly different from languages like JavaScript.

    In C language, callback functions are typically implemented using function pointers. You can pass function pointers as parameters to another function and call them at the appropriate time.

    In other words, the concept of a system centered around callback functions arises, and the core of this system is the callback function, which is crucial for the subsequent work and learning.

    Today’s sharing ends here. I will continue to share other content as I learn more. In the next session, I will share the core content of C language, which is callback functions.

    Leave a Comment