How do function pointers in C work?

Keywords: Windows REST C

I've recently had some experience with function pointers in C.

So, continuing with the tradition of answering your own questions, I decided to make some basic summaries for those who need to learn the topic quickly.

#1 building

Another good use of function pointers:
Easy to switch versions

When you need different functions at different times or in different development stages, they are very convenient to use. For example, I am developing an application on a host with a console, but the final version of the software will be placed on Avnet ZedBoard (the port has ports for display and console, but they are not needed / needed). Final version). So, during development, I'll use printf to see status and error messages, but when I'm done, I don't want to print anything. This is what I did:

Edition

// First, undefine all macros associated with version.h
#undef DEBUG_VERSION
#undef RELEASE_VERSION
#undef INVALID_VERSION


// Define which version we want to use
#define DEBUG_VERSION       // The current version
// #define RELEASE_VERSION  // To be uncommented when finished debugging

#ifndef __VERSION_H_      /* prevent circular inclusions */
    #define __VERSION_H_  /* by using protection macros */
    void board_init();
    void noprintf(const char *c, ...); // mimic the printf prototype
#endif

// Mimics the printf function prototype. This is what I'll actually 
// use to print stuff to the screen
void (* zprintf)(const char*, ...); 

// If debug version, use printf
#ifdef DEBUG_VERSION
    #include <stdio.h>
#endif

// If both debug and release version, error
#ifdef DEBUG_VERSION
#ifdef RELEASE_VERSION
    #define INVALID_VERSION
#endif
#endif

// If neither debug or release version, error
#ifndef DEBUG_VERSION
#ifndef RELEASE_VERSION
    #define INVALID_VERSION
#endif
#endif

#ifdef INVALID_VERSION
    // Won't allow compilation without a valid version define
    #error "Invalid version definition"
#endif

In version.c, I will define two function prototypes that exist in version.h

Version number

#include "version.h"

/*****************************************************************************/
/**
* @name board_init
*
* Sets up the application based on the version type defined in version.h.
* Includes allowing or prohibiting printing to STDOUT.
*
* MUST BE CALLED FIRST THING IN MAIN
*
* @return    None
*
*****************************************************************************/
void board_init()
{
    // Assign the print function to the correct function pointer
    #ifdef DEBUG_VERSION
        zprintf = &printf;
    #else
        // Defined below this function
        zprintf = &noprintf;
    #endif
}

/*****************************************************************************/
/**
* @name noprintf
*
* simply returns with no actions performed
*
* @return   None
*
*****************************************************************************/
void noprintf(const char* c, ...)
{
    return;
}

Notice how function pointers are prototyped in version.h to

void (* zprintf)(const char *, ...);

When it is referenced in an application, it will begin execution anywhere it points to (not defined yet).

In version.c, notice the functions in the board init() function, where zprintf is assigned a unique function based on the version defined by version.h (its function signature matches)

Zprintf = & printf; zprintf calls printf for debugging

Or

Zprintf = & noprint; zprintf just returns without running unnecessary code

The running code is as follows:

main program

#include "version.h"
#include <stdlib.h>
int main()
{
    // Must run board_init(), which assigns the function
    // pointer to an actual function
    board_init();

    void *ptr = malloc(100); // Allocate 100 bytes of memory
    // malloc returns NULL if unable to allocate the memory.

    if (ptr == NULL)
    {
        zprintf("Unable to allocate memory\n");
        return 1;
    }

    // Other things to do...
    return 0;
}

The above code will use printf in debug mode and do nothing in release mode. This is much easier than walking through the entire project and commenting out or deleting code. All I have to do is change version.h in version.h, and the rest of the code will be finished!

#2 building

The start from scratch function has some memory addresses at the location from which it starts execution. In assembly languages, they are called (called "the memory address of a function"). Now return C, and if the functions have memory addresses, they can be manipulated by points in C. Therefore, according to the rules of C

1. First you need to declare a pointer to the function 2. Pass the address of the required function

****Note - > function should be of the same type****

This simple program will explain everything.

#include<stdio.h>
void (*print)() ;//Declare a  Function Pointers
void sayhello();//Declare The Function Whose Address is to be passed
                //The Functions should Be of Same Type
int main()
{
 print=sayhello;//Addressof sayhello is assigned to print
 print();//print Does A call To The Function 
 return 0;
}

void sayhello()
{
 printf("\n Hello World");
}

After that, let's see how machines understand them. The list of machine instructions in the 32-bit architecture of the above program.

The red flag area shows how to exchange addresses and store them in eax. Then, they are call instructions on eax. Eax contains the required address of the function.

#3 building

Function pointers are usually defined by typedef and are used as parameters and return values.

The above answers have been explained a lot, I just give a complete example:

#include <stdio.h>

#define NUM_A 1
#define NUM_B 2

// define a function pointer type
typedef int (*two_num_operation)(int, int);

// an actual standalone function
static int sum(int a, int b) {
    return a + b;
}

// use function pointer as param,
static int sum_via_pointer(int a, int b, two_num_operation funp) {
    return (*funp)(a, b);
}

// use function pointer as return value,
static two_num_operation get_sum_fun() {
    return &sum;
}

// test - use function pointer as variable,
void test_pointer_as_variable() {
    // create a pointer to function,
    two_num_operation sum_p = &sum;
    // call function via pointer
    printf("pointer as variable:\t %d + %d = %d\n", NUM_A, NUM_B, (*sum_p)(NUM_A, NUM_B));
}

// test - use function pointer as param,
void test_pointer_as_param() {
    printf("pointer as param:\t %d + %d = %d\n", NUM_A, NUM_B, sum_via_pointer(NUM_A, NUM_B, &sum));
}

// test - use function pointer as return value,
void test_pointer_as_return_value() {
    printf("pointer as return value:\t %d + %d = %d\n", NUM_A, NUM_B, (*get_sum_fun())(NUM_A, NUM_B));
}

int main() {
    test_pointer_as_variable();
    test_pointer_as_param();
    test_pointer_as_return_value();

    return 0;
}

#4 building

One of the main uses of function pointers in C is to call the function selected at run time. For example, the C runtime library has two routines qsort and bsearch , they use a pointer to a function that is called to compare the two items to sort. This allows you to sort or search any content individually based on any criteria you want to use.

A very basic example, if there is a function named print(int x, int y), and the function may need to call a function (with the same type of add() or sub()), then what we will do, we will add a function pointer parameter to the print() function, as follows:

#include <stdio.h>

int add()
{
   return (100+10);
}

int sub()
{
   return (100-10);
}

void print(int x, int y, int (*func)())
{
    printf("value is: %d\n", (x+y+(*func)()));
}

int main()
{
    int x=100, y=200;
    print(x,y,add);
    print(x,y,sub);

    return 0;
}

Output is:

The value is: 410
The value is: 390

#5 building

A function pointer is a variable that contains the address of a function. Because it is a pointer variable, but has some restricted properties, you can use it just like any other pointer variable in the data structure.

The only exception I can think of is to think of function pointers as pointing to something other than a single value. Pointer arithmetic by incrementing or decrementing function pointers or increasing / subtracting offsets from function pointers is not a real utility, because function pointers only point to a single object, the entry point of a function.

The size of the function pointer variable, which may take up different bytes depending on the infrastructure, such as x32 or x64 or others.

The declaration of a function pointer variable needs to specify the same information as the function declaration so that the C compiler can perform the usual checks. If you do not specify a parameter list in the declaration / definition of a function pointer, the C compiler cannot check the use of parameters. In some cases, a lack of checks is useful, but remember that the safety net has been removed.

Some examples:

int func (int a, char *pStr);    // declares a function

int (*pFunc)(int a, char *pStr);  // declares or defines a function pointer

int (*pFunc2) ();                 // declares or defines a function pointer, no parameter list specified.

int (*pFunc3) (void);             // declares or defines a function pointer, no arguments.

The first two statements are similar in that:

  • func is a function that accepts int and char * and returns int
  • pFunc is a function pointer. The address of the function pointing to the function uses int and char * and returns int

Therefore, we can get a source code line from the above, in which the address of func() function is assigned to the function pointer variable pFunc, such as pFunc = func.

Note the syntax used for function pointer declaration / definition, where parentheses are used to overcome natural operator precedence rules.

int *pfunc(int a, char *pStr);    // declares a function that returns int pointer
int (*pFunc)(int a, char *pStr);  // declares a function pointer that returns an int

Several different usage examples

Some examples of using function pointers:

int (*pFunc) (int a, char *pStr);    // declare a simple function pointer variable
int (*pFunc[55])(int a, char *pStr); // declare an array of 55 function pointers
int (**pFunc)(int a, char *pStr);    // declare a pointer to a function pointer variable
struct {                             // declare a struct that contains a function pointer
    int x22;
    int (*pFunc)(int a, char *pStr);
} thing = {0, func};                 // assign values to the struct variable
char * xF (int x, int (*p)(int a, char *pStr));  // declare a function that has a function pointer as an argument
char * (*pxF) (int x, int (*p)(int a, char *pStr));  // declare a function pointer that points to a function that has a function pointer as an argument

You can use variable length parameter lists in the definition of function pointers.

int sum (int a, int b, ...);
int (*psum)(int a, int b, ...);

Or you cannot specify a parameter list at all. This may be useful, but it eliminates the opportunity for the C compiler to perform a check on the supplied parameter list.

int  sum ();      // nothing specified in the argument list so could be anything or nothing
int (*psum)();
int  sum2(void);  // void specified in the argument list so no parameters when calling this function
int (*psum2)(void);

C-style cast

You can use C-style casts with function pointers. Note, however, that the C compiler may be lax in checking, or provide warnings rather than errors.

int sum (int a, char *b);
int (*psplsum) (int a, int b);
psplsum = sum;               // generates a compiler warning
psplsum = (int (*)(int a, int b)) sum;   // no compiler warning, cast to function pointer
psplsum = (int *(int a, int b)) sum;     // compiler error of bad cast generated, parenthesis are required.

Compare function pointer and equality

You can use the if statement to check that the function pointer is equal to a specific function address, although I'm not sure it's useful. The utility of other comparison operators seems to be lower.

static int func1(int a, int b) {
    return a + b;
}

static int func2(int a, int b, char *c) {
    return c[0] + a + b;
}

static int func3(int a, int b, char *x) {
    return a + b;
}

static char *func4(int a, int b, char *c, int (*p)())
{
    if (p == func1) {
        p(a, b);
    }
    else if (p == func2) {
        p(a, b, c);      // warning C4047: '==': 'int (__cdecl *)()' differs in levels of indirection from 'char *(__cdecl *)(int,int,char *)'
    } else if (p == func3) {
        p(a, b, c);
    }
    return c;
}

Function pointer array

Also, if you want to have an array of function pointers with different elements in each parameter list, you can define a function pointer, where the parameter list is not specified (not void, which means no parameters, just not specified), as shown below you may see a warning from the C compiler. This also applies to function pointer parameters for functions:

int(*p[])() = {       // an array of function pointers
    func1, func2, func3
};
int(**pp)();          // a pointer to a function pointer


p[0](a, b);
p[1](a, b, 0);
p[2](a, b);      // oops, left off the last argument but it compiles anyway.

func4(a, b, 0, func1);
func4(a, b, 0, func2);  // warning C4047: 'function': 'int (__cdecl *)()' differs in levels of indirection from 'char *(__cdecl *)(int,int,char *)'
func4(a, b, 0, func3);

    // iterate over the array elements using an array index
for (i = 0; i < sizeof(p) / sizeof(p[0]); i++) {
    func4(a, b, 0, p[i]);
}
    // iterate over the array elements using a pointer
for (pp = p; pp < p + sizeof(p)/sizeof(p[0]); pp++) {
    (*pp)(a, b, 0);          // pointer to a function pointer so must dereference it.
    func4(a, b, 0, *pp);     // pointer to a function pointer so must dereference it.
}

C-style namespace uses global struct with function pointer

You can use the static keyword to specify a function whose name is file scoped, and then assign it to a global variable to provide a way similar to the C + + namespace feature.

Define a structure in the header file that will become our namespace and the global variable that uses it.

typedef struct {
   int (*func1) (int a, int b);             // pointer to function that returns an int
   char *(*func2) (int a, int b, char *c);  // pointer to function that returns a pointer
} FuncThings;

extern const FuncThings FuncThingsGlobal;

Then in the C source file:

#include "header.h"

// the function names used with these static functions do not need to be the
// same as the struct member names. It's just helpful if they are when trying
// to search for them.
// the static keyword ensures these names are file scope only and not visible
// outside of the file.
static int func1 (int a, int b)
{
    return a + b;
}

static char *func2 (int a, int b, char *c)
{
    c[0] = a % 100; c[1] = b % 50;
    return c;
}

const FuncThings FuncThingsGlobal = {func1, func2};

Then, access the function by specifying the full name and member name of the global struct variable. The const modifier is used for global variables, so it is not accidentally changed.

int abcd = FuncThingsGlobal.func1 (a, b);

Application field of function pointer

DLL library components can perform operations similar to the C-style namespace method. In the c-namespace method, a specific library interface is requested from the factory method in the library interface, which supports the creation of structs containing function pointers. . the library interface loads the requested DLL version. , create a structure with the necessary function pointers, and then return the structure to the caller making the request for use.

typedef struct {
    HMODULE  hModule;
    int (*Func1)();
    int (*Func2)();
    int(*Func3)(int a, int b);
} LibraryFuncStruct;

int  LoadLibraryFunc LPCTSTR  dllFileName, LibraryFuncStruct *pStruct)
{
    int  retStatus = 0;   // default is an error detected

    pStruct->hModule = LoadLibrary (dllFileName);
    if (pStruct->hModule) {
        pStruct->Func1 = (int (*)()) GetProcAddress (pStruct->hModule, "Func1");
        pStruct->Func2 = (int (*)()) GetProcAddress (pStruct->hModule, "Func2");
        pStruct->Func3 = (int (*)(int a, int b)) GetProcAddress(pStruct->hModule, "Func3");
        retStatus = 1;
    }

    return retStatus;
}

void FreeLibraryFunc (LibraryFuncStruct *pStruct)
{
    if (pStruct->hModule) FreeLibrary (pStruct->hModule);
    pStruct->hModule = 0;
}

This can be used to:

LibraryFuncStruct myLib = {0};
LoadLibraryFunc (L"library.dll", &myLib);
//  ....
myLib.Func1();
//  ....
FreeLibraryFunc (&myLib);

You can use the same method to define an abstract hardware layer for code that uses a specific model of the underlying hardware. The factory populates function pointers with function specific functions to provide functions that implement the functions specified in a specific Abstract hardware model. This can be used to provide an abstract hardware layer used by software that calls factory functions to obtain specific hardware function interfaces, and then uses the provided function pointers to perform operations on the underlying hardware without knowing the implementation details of specific goals.

Function pointers for creating delegates, handlers, and callbacks

You can use function pointers to delegate certain tasks or functions. The classic example in C language is a comparison delegate function pointer used with the standard C library functions qsort() and bsearch() to provide a sort order for sorting item lists or binary searches of item lists. The comparison function delegates the collation algorithm used in the specified sort or binary search.

Another use is similar to applying algorithms to the C + + standard template library.

void * ApplyAlgorithm (void *pArray, size_t sizeItem, size_t nItems, int (*p)(void *)) {
    unsigned char *pList = pArray;
    unsigned char *pListEnd = pList + nItems * sizeItem;
    for ( ; pList < pListEnd; pList += sizeItem) {
        p (pList);
    }

    return pArray;
}

int pIncrement(int *pI) {
    (*pI)++;

    return 1;
}

void * ApplyFold(void *pArray, size_t sizeItem, size_t nItems, void * pResult, int(*p)(void *, void *)) {
    unsigned char *pList = pArray;
    unsigned char *pListEnd = pList + nItems * sizeItem;
    for (; pList < pListEnd; pList += sizeItem) {
        p(pList, pResult);
    }

    return pArray;
}

int pSummation(int *pI, int *pSum) {
    (*pSum) += *pI;

    return 1;
}

// source code and then lets use our function.
int intList[30] = { 0 }, iSum = 0;

ApplyAlgorithm(intList, sizeof(int), sizeof(intList) / sizeof(intList[0]), pIncrement);
ApplyFold(intList, sizeof(int), sizeof(intList) / sizeof(intList[0]), &iSum, pSummation);

Another example is GUI source code, which registers the handler for a specific event by providing a function pointer that is actually called when the event occurs. The Microsoft MFC framework and its message mapping use similar methods to handle Windows messages delivered to Windows or threads.

Asynchronous functions that require callbacks are similar to event handlers. The user of the asynchronous function calls the asynchronous function to start some operations and provides a function pointer. Once the operation is completed, the asynchronous function will call the function pointer. In this case, events are asynchronous functions that complete their tasks.

Posted by funkyres on Tue, 10 Dec 2019 22:05:16 -0800