简体   繁体   中英

How to explain calling a C function from an array of function pointers?

This example https://godbolt.org/z/EKvvEKa6T calls MyFun() using this syntax

(*((int(**)(void))CallMyFun))();

Is there a C breakdown of that obfuscated syntax to explain how it works?

#include <stdio.h>

int MyFun(void)
{
    printf("Hello, World!");
    return 0;
}

void *funarray[] = { NULL,NULL,&MyFun,NULL,NULL };

int main(void)
{
    size_t CallMyFun = (size_t)&funarray + (2 * sizeof(funarray[0]));
    return (*((int(**)(void))CallMyFun))();
}

The behavior of void *funarray[] = { NULL,NULL,&MyFun,NULL,NULL }; is not defined by the C standard because it converts a pointer to a function ( MyFun ) to a pointer to an object type ( void * ). Conversions to pointers are specified in C 2018 6.3.2.3, and none of them cover conversions between pointers to object types and pointers to function types except for null pointers.

The code size_t CallMyFun = (size_t)&funarray + (2 * sizeof(funarray[0])); sets CallMyFun to the address of element 2 of funarray , provided that conversion of pointers to the integer size_t type works “naturally,” which is not required by the C standard but is intended.

In (*((int(**)(void))CallMyFun))(); , we have (int(**)(void)) CallMyFun . This says to convert CallMyFun to a pointer to a pointer to a function taking no arguments and returning an int . When using a table of heterogenous function pointers in C, it is necessary to convert between types, because C provides no generic function pointer mechanism, so we cannot fault the author for that. However, this converts not merely to a pointer to a function type but to a pointer to a pointer to a function type, and that is an error.

The array contains pointers to void , not pointers to pointers to functions. The C standard does not require these to have the same size or representation.

Then the code applies * , intending to retrieve the pointer. This is another error. If if a pointer to void had the same size and representation has a pointer to a pointer to a function, accessing a void * (which is what was stored in the array) as a pointer to a function (which is what retrieving it using this expression does) violates the aliasing rules in C 2018 6.5 7.

However, if the C implementation does support this and all the prior conversions and issues, the result is a pointer to the function MyFun , and then applying () calls the function.

A proper way to write the code, assuming the array must support heterogenous function types, could be:

// Use array of pointers to functions (of a forced type).
void (*funarray[])(void) = { NULL, NULL, (void (*)(void)) MyFun, NULL, NULL };

…

// Get array element using ordinary subscript notation.
void (*CallMyFunc)(void) = funarray[2];

// Convert pointer to function’s actual type, then call it.
return ((int (*)(void)) CallMyFunc)();

If the array can be homogenous, then the code should be written:

int (*funarray[])(void) = { NULL, NULL, MyFun, NULL, NULL };

…

return funarray[2]();

To understand what's going on here, we should look at each part of the code piece by piece (at least the odd pieces):

void *funarray[] = { NULL,NULL,&MyFun,NULL,NULL };

In the above, we are creating an array of pointers initialized to be most "empty" (ie, NULL ) but at index 2 we have a pointer to MyFunc . It's helpful to track the types as we go, so at this point, we have &MyFunc of type int (*)(void) (function pointer syntax in C is a bit odd as the * and possible identifier goes in the middle rather than at the end), which is immediately turned into a void * in the array. Why the array is of type void * when it appears to be containing function pointers is a bit odd but let's assume there is a good reason...

size_t CallMyFun = (size_t)&funarray + (2 * sizeof(funarray[0]));

This code is a rather complex way of accessing the array and getting the second element by converting the head of the array to a size_t then adding the correct number of bytes to get the address of the second entry (a simpler way would be (size_t) &funarray[2] ). Here again, we have lost the type information, going from originally a int (*[])(void) to void*[] to size_t .

return (*((int(**)(void))CallMyFun))();

Now that we know that CallMyFunc contains a pointer to a function pointer, ie the address of the MyFunc pointer in the array, if we want to call it we need to convert it back to the proper type and dereference it correctly. So, unwrapping the call, we direct have ((int(**)(void))CallMyFun) which casts the improperly typed CallMyFunc from size_t to a double function pointer, ie a pointer to a function pointer, which the syntax for is int (**)(void) , just like any other double pointer which needs two * to denote. Next, as is isn't yet a function pointer, we need to dereference it to get the pointer of type int (*)(void) , so (*((int(**)(void))CallMyFun)) . Finally, we want to actually call the function pointer so the last set of parents.

As mentioned in the comments, this code is rather obfuscated by changing types and using unusual ways of accessing arrays; it's usually best to keep types consistent as it lets the compiler help you avoid mistakes and makes the code more readable for yourself and others.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM