Operating System 11 | Function Pointer, Callback, Malloc, and Macro Programming

Series: Operating System

Operating System 11 | Function Pointer, Callback, Malloc, and Macro Programming

  1. Function Pointer

(1) Function Pointer

Sometimes we may want a pointer pointing to a function. For example, suppose we a function int add(int a, int b) that will return the value of a + b . We have known that the add variable is actually the pointer value of this function, so we may simply have the idea that we can build an integer pointer to store this function. For instance,

int *p = add;

Regardless of the warning of this line, this assigned statement can work for us and we will actually get a pointer pointing to the address add. But could this pointer know that it is pointing to a function? The answer is no. If we define the pointer in that way, the pointer be a pointer pointing towards an integer instead of a function. Thus, it will explain the data at the address data as an integer and this attempt will definitely fail.

The function pointer is a pointer that points to a function. So when we use a function pointer, it actually knows that it is going to point to a function. For our add function case, we can define a function pointer p as,

int (*p)(int, int) = add;

After that, the pointer p will be pointing to the address of the add function and we can use it exactly the same as the add. Here’s an example code,

The output of this code might (note the address can be different),

0x10f6d5ef0: 5
0x10f6d5ef0: 7

(2) Callback Function

Now, we may have a problem. Why don’t we use add directly in the last example? Well, the answer is that we can use only add in the last example, while in reality, we do have some situations when we need the function pointer. Let’s see an example,

Suppose we have the following code,

The output should be,

3
-1

For func1 and func2, the functions compare a and b in the first place, and then return a + b or a — b based on the corresponding comparison result. If we have these needs, it seems that we must create two separate functions even these functions are quite similar. When there are more codes for each function, it will not be a good idea if we create different functions with the almost same content. In order to reuse the code, we can think about using the function pointer as a parameter of our function. This function is then called a callback function because there are some executable codes passed as an argument to other code. So the code will be,

This is convenient for us because when we want to change the condition (for example, we may want to add a comparison of 2*a > b) of the func function, we can simply add another function (for example, foo3) and pass the name of this function as an argument of the func function.

(3) Return Function Pointer

Suppose we have defined a function foo and then we want to return the pointer of this function when we call func function in the main function. So we have to figure out what we can write to return a function pointer. In the following code, we simply use int for the func , which is definitely wrong.

#include <stdio.h>
int foo(int a, int b) {
return a > b;
}
// the following func function is wrong
int func() {
return foo;
}
int main(void) {

return 0;
}

Someone may think that we can use the following code,

int (*)(int, int) func() {
return foo;
}

Even though the code above seems clear to us, the compiler will not return the right function pointer for us. Instead, what we should use is,

int (*func())(int, int) {
return foo;
}

This can be hard to remember and difficult to type if we have to return the function pointers for several functions. To deal with the complexity, we can use typedef to define a new function pointer datatype,

typedef int (*fp)(int, int);

Then we can simply return the fp type for the func function,

fp func() {
return foo;
}

The example code is as follows,

2. Malloc

Suppose we have defined a structure foo with two elements int a[10]; and char b[20]; by,

typedef struct foo {
int a[10];
char b[20];
}foo;

Then if we want to create an instance of this structure, we may think about using,

foo struct1;

However, when we do so, 60 bytes ( = 4*10 + 1*20) of memory will be directly allocated to this instance struct1. It is a pity that we have this memory occupied when we create this instance and we can not reallocate this memory until the end of the current function (or maybe the program if this instance is created in main).

Instead, we want to allocate memory to this structure only when we need it and we want to free the memory of this structure when we finish our task. So what we can do is to allocate the memory in a dynamic way. To dynamically allocate a segment of memory, we can use the malloc function,

foo *struct1 = (foo *)malloc(sizeof(foo));

When we do so, we must remember to free the allocated memory after we finish our task (or at the end of our program).

free(struct1);

3. Macro Programming Introduction

In the previous examples, we have defined macros by,

#define BUF_SIZE 20

A macro in computer science is a rule or pattern that specifies how a certain input should be mapped to a replacement output. When we define the BUF_SIZE , we are actually mapping this value to 20 by #define.

In the C language, we have know that different objected files have to be linked together to create an executable binary file. If we have a macro defined with different values, there will be a macro confliction. For example if we define,

// macro.c
#define SYSTEM "MACOS"
// macro.h
#define SYSTEM "WINDOWS"

Then the linker can not decide which macro value to use. To resolve this confliction, we have to use either #ifndef or #undef. These two directives serves for different purposes.

#ifndef means if not defined. So if the macro after #ifndef is not defined, we are going to run the following macro directives until #endif . If we have the following code,

The output should be,

MACOS

#undef means undefined. Usually, we would first use #ifdef to see whether or not a macro is defined. Then if it is defined, we would undefine this macro and then redefine its value. So if we have the following code,

The output should be,

WINDOWS