Function Pointers Casting in C++

Casting a function pointer to another type

As far as the C standard is concerned, if you cast a function pointer to a function pointer of a different type and then call that, it is undefined behavior. See Annex J.2 (informative):

The behavior is undefined in the following circumstances:

  • A pointer is used to call a function whose type is not compatible with the pointed-to
    type (6.3.2.3).

Section 6.3.2.3, paragraph 8 reads:

A pointer to a function of one type may be converted to a pointer to a function of another
type and back again; the result shall compare equal to the original pointer. If a converted
pointer is used to call a function whose type is not compatible with the pointed-to type,
the behavior is undefined.

So in other words, you can cast a function pointer to a different function pointer type, cast it back again, and call it, and things will work.

The definition of compatible is somewhat complicated. It can be found in section 6.7.5.3, paragraph 15:

For two function types to be compatible, both shall specify compatible return types127.

Moreover, the parameter type lists, if both are present, shall agree in the number of
parameters and in use of the ellipsis terminator; corresponding parameters shall have
compatible types. If one type has a parameter type list and the other type is specified by a
function declarator that is not part of a function definition and that contains an empty
identifier list, the parameter list shall not have an ellipsis terminator and the type of each
parameter shall be compatible with the type that results from the application of the
default argument promotions. If one type has a parameter type list and the other type is
specified by a function definition that contains a (possibly empty) identifier list, both shall
agree in the number of parameters, and the type of each prototype parameter shall be
compatible with the type that results from the application of the default argument
promotions to the type of the corresponding identifier. (In the determination of type
compatibility and of a composite type, each parameter declared with function or array
type is taken as having the adjusted type and each parameter declared with qualified type
is taken as having the unqualified version of its declared type.)

127) If both function types are ‘‘old style’’, parameter types are not compared.

The rules for determining whether two types are compatible are described in section 6.2.7, and I won't quote them here since they're rather lengthy, but you can read them on the draft of the C99 standard (PDF).

The relevant rule here is in section 6.7.5.1, paragraph 2:

For two pointer types to be compatible, both shall be identically qualified and both shall be pointers to compatible types.

Hence, since a void* is not compatible with a struct my_struct*, a function pointer of type void (*)(void*) is not compatible with a function pointer of type void (*)(struct my_struct*), so this casting of function pointers is technically undefined behavior.

In practice, though, you can safely get away with casting function pointers in some cases. In the x86 calling convention, arguments are pushed on the stack, and all pointers are the same size (4 bytes in x86 or 8 bytes in x86_64). Calling a function pointer boils down to pushing the arguments on the stack and doing an indirect jump to the function pointer target, and there's obviously no notion of types at the machine code level.

Things you definitely can't do:

  • Cast between function pointers of different calling conventions. You will mess up the stack and at best, crash, at worst, succeed silently with a huge gaping security hole. In Windows programming, you often pass function pointers around. Win32 expects all callback functions to use the stdcall calling convention (which the macros CALLBACK, PASCAL, and WINAPI all expand to). If you pass a function pointer that uses the standard C calling convention (cdecl), badness will result.
  • In C++, cast between class member function pointers and regular function pointers. This often trips up C++ newbies. Class member functions have a hidden this parameter, and if you cast a member function to a regular function, there's no this object to use, and again, much badness will result.

Another bad idea that might sometimes work but is also undefined behavior:

  • Casting between function pointers and regular pointers (e.g. casting a void (*)(void) to a void*). Function pointers aren't necessarily the same size as regular pointers, since on some architectures they might contain extra contextual information. This will probably work ok on x86, but remember that it's undefined behavior.

Typecasting a function pointer

There are functions and there are function pointers. strcmp is a function, linked to your project. When the identifier strcmp is used in an expression, you get a function pointer to that function. But you cannot change "where strcmp points to" because strcmp is a function and not a pointer.

C allows conversions between different function pointer types. However, should you try to call a function through a function pointer of incompatible type, you invoke undefined behavior.

int (*funcPtr)(void*,void*);

is not compatible with strcmp, which has the following format:

int strcmp(const char *s1, const char *s2);

Meaning:

funcPtr = (int(*)(void*, void*))strcmp; // this is ok but not very meaningful
funcPtr(); // this is NOT ok, invokes undefined behavior

In order to actually call the function strcmp through that function pointer, you would have to cast back to the correct type, which is int (*)(const char*, const char*).

Is it safe to cast a function pointer to another function pointer in C?


Is it safe to cast these 2 functions to callback pointer type then call them without casting back?

No. The types of the two functions are not compatible with type callback, in the language specification's sense of "compatible", therefore calling either of those functions via a pointer of type callback invokes undefined behavior. Overall, non-variadic function types are never compatible with variadic ones, and in practice, many implementations use different calling conventions for one type than for the other, such that there is no plausible reason even to hope that calling a function of one variety as if it were of the other variety would have the desired effect in any consistent way.

You have several alternatives, among them:

  • Use different callback types for different purposes, each appropriate to its intended callback interface. This way you can avoid casting the callback functions at all. This would be my recommendation. It achieves the best type safety, and you need somehow to keep track of what the actual callback type is anyway, so that you can call it correctly.

  • Use a union of function pointer types. Callback specifiers assign to the appropriate member of the union, and callback callers select the appropriate member.

    typedef union {
    int (*unary)(int i);
    int (*binary)(int i, int j);
    } callback;

    // ...

    callback cb1 = { .unary = foo };
    callback cb2 = { .binary = foo2 };
    cb1.unary(1);
    cb2.binary(1, 2);

    You might even use a tagged union -- one that additionally carries information about which member is used. That would be a bit more complicated to use, but it would give you a means to achieve additional type safety. One of the variations on this approach would be my fallback recommendation if you need a single data type with which multiple callback types can be conveyed.

  • Choose a single callback type that meets all your needs. One way to do that would be to give it a parameter of type void *, by which callback functions can accept any number and type of inputs by, for example, a pointer to a suitable structure type.

    typedef int (*callback)(void *);
    struct one_int { int i1; };
    struct two_int { int i1, i2; };
    int foo(void *args) {
    struct one_int *one_int = args; // ...
    }
    int foo2(void *args) {
    struct two_int *two_int = args; // ...
    }
  • Choose any function type as callback. Cast to that type going in, and back to the original type for calls.

  • Specify the callback type without a prototype. In C, if a function declaration that is not part of a definition of that function does not specify a parameter type list then that means that no information is provided about the parameters (unlike in C++, where that means that the function has no parameters). That is compatible with functions requiring any specific number of arguments -- but not variadic ones -- provided that applying the default argument promotions to the parameter types yields compatible types. Type int is a fine parameter type in that regard. The main ones that would be a problem are integer types narrower than int, plus float.

    typedef int (*callback)();

    This would allow exactly the usage you describe for the particular function types in your example.

    callback cb1 = foo;
    callback cb2 = foo2;

    (*cb1)(1); // or just cb1(1)
    (*cb2)(1, 2); // or just cb2(1, 2)

    Contrary to another answer's claim, support for this approach does not constitute an extension to any version of the C language specification published to date. Supporting it is a requirement for conformance with any of C89, C99, C11, and C17. However, it has been declared "obsolescent" in C17, which constitutes a warning that it may be removed from some future version of the language specification. I expect that it indeed will be removed, possibly as soon as the next version of the specification, though obsolescence does not guarantee that.

Casting A Function Pointers Return Type in C

The C11 standard §6.2.5 Types ¶28 says:

A pointer to void shall have the same representation and alignment requirements as a
pointer to a character type.48) Similarly, pointers to qualified or unqualified versions of
compatible types shall have the same representation and alignment requirements. All
pointers to structure types shall have the same representation and alignment requirements
as each other. All pointers to union types shall have the same representation and
alignment requirements as each other. Pointers to other types need not have the same
representation or alignment requirements.

That means, in particular, that pointers to functions do not have to have the same representation as any pointer to object type. There are, or at least were, machines where pointers to functions were bigger than pointers to objects (and I'm not referring to small, medium, compact, large, huge models and 80x86 chips — I believe IBM AS/400, later iSeries, had pointers to functions that were bigger than any pointer to object). Also, as Ajay Brahmakshatriya points out in a comment, some ABIs (application binary interfaces) can have different calling conventions for different function types, so converting a function pointer to the 'wrong' type and then invoking it via that converted pointer may end up wreaking havoc too. This particularly applies to pointers versus floating point versus other types. Usually (but it is by no means guaranteed), functions returning pointers will return the pointer the same way regardless of the type of the pointer returned.

And §6.3.2.3 Pointers ¶7-8 says:

A pointer to an object type may be converted to a pointer to a different object type. If the
resulting pointer is not correctly aligned68) for the referenced type, the behavior is
undefined. Otherwise, when converted back again, the result shall compare equal to the
original pointer. When a pointer to an object is converted to a pointer to a character type,
the result points to the lowest addressed byte of the object. Successive increments of the
result, up to the size of the object, yield pointers to the remaining bytes of the object.

A pointer to a function of one type may be converted to a pointer to a function of another
type and back again; the result shall compare equal to the original pointer. If a converted
pointer is used to call a function whose type is not compatible with the referenced type,
the behavior is undefined.

As you wrote the code, you'd be calling the function through the cast type, which would lead to undefined behaviour. It might work, but it might stop working and you'd have no recourse except to rewrite the code to avoid the undefined behaviour.

This means that you are not supposed to write your front-end function pointers as shown in the question. You could use simple cover functions, though, to do the job you want:

static int *addIntFunc(void *mem, size_t old_size, size_t extra_size)
{
return addElement(mem, old_size, extra_size);
}

int *(*addInt)(void *, size_t, size_t) = addIntFunc;

You probably can't usefully inline the function; a function pointer must point at a function.

Or, indeed, you could simply make use of addIntFunc (possibly renamed to addInt) to get the required effect, and then an inlined function might well be useful.

Is casting a function pointer that takes a const pointer argument to the equivalent function but with non constant pointer arguments OK?

This is not allowed because the types of one of the corresponding parameters is not compatible.

Compatible types are defined in section 6.2.7p1 of the C standard:

Two types have compatible type if their types are the same. Additional
rules for determining whether two types are compatible are described
in 6.7.2 for type specifiers, in 6.7.3 for type qualifiers, and in
6.7.6 for declarators. ...

And section 6.7.3p10 details compatibility of qualified types:

For two qualified types to be compatible, both shall have the
identically qualified version of a compatible type; the order of type
qualifiers within a list of specifiers or qualifiers does not affect
the specified type.

This means that const void * and void * are not compatible.

Compatibility of function types is described in section 6.7.6.3p15:

For two function types to be compatible, both shall specify compatible
return types. Moreover, the parameter type lists, if both are
present, shall agree in the number of parameters and in use of the
ellipsis terminator; corresponding parameters shall have compatible
types.
If one type has a parameter type list and the other type is
specified by a function declarator that is not part of a function
definition and that contains an empty identifier list, the parameter
list shall not have an ellipsis terminator and the type of each
parameter shall be compatible with the type that results from the
application of the default argument promotions. If one type has a
parameter type list and the other type is specified by a function
definition that contains a (possibly empty) identifier list, both
shall agree in the number of parameters, and the type of each
prototype parameter shall be compatible with the type that results
from the application of the default argument promotions to the type of
the corresponding identifier. (In the determination of type
compatibility and of a composite type, each parameter declared with
function or array type is taken as having the adjusted type and each
parameter declared with qualified type is taken as having the
unqualified version of its declared type.)

So because one set of corresponding parameters are not compatible, the function types are not compatible.

Finally, section 6.5.2.2p9 regarding the function call operator () describes what happens in this case:

If the function is defined with a type that is not compatible with the
type (of the expression) pointed to by the expression that denotes the
called function, the behavior is undefined.

So calling a function through an incompatible function pointer type triggers undefined behavior and therefore should not be done.

Function pointer casting

Well, casting should work. The C standard allows casting between function pointer types, the only thing you shouldn't do is call a function by a function pointer that has the wrong prototype, i.e. cast back to the correct prototype before the call, in order to avoid undefined behavior.

Let's start by simplifying your arrays a little, for readability sake:

modbus_read_t*  M_callbacks_r[2];
modbus_write_t* M_callbacks_w[2];
system_read_t* P_callbacks_r[2];
system_write_t* P_callbacks_w[2];

I don't know why you typedefed the function type instead of the function pointer type, but I'll go along with it.

Now registerCallback can be implemented as follows:

void registerCallback(int systemType,int operationType, void (*callback)()) {
// .. determine array by system type and operation type
M_callbacks_r[0] = (modbus_read_t*)callback;
}

The reason I deleted the parameter from the callback prototype, is that in standard C, an empty parameter list conveys no information about the expected parameters (as opposed to C++ where it explicitly means "no parameters"). That way callbacks can be registered without requiring a cast.


As you mentioned in the comments, you are building with -Wstrict-prototypes -Werror. So an empty parameter list will not work for you. You are thus restricted to defining registerCallback like this:

void registerCallback(int systemType,int operationType, void (*callback)(void))

Note the explicit void in the parameter list. You will need to cast the callback when passing it to registerCallback as well as casting it back inside the function itself.

registerCallback(type, op, (void(*)(void))func_cb);

Side note: The _t postfix is reserved on POSIX systems. To be portable, it may be best not to name your types with it.

Casting function pointer arguments without a helper function

Your attempt at the cast simply has a misplaced right (closing) parenthesis. The one at the end should be after the type of the cast. So, you can change:

(int (*) (const void*, const void*) (strcmp))
// ^ wrong

to

(int (*) (const void*, const void*)) (strcmp)
// ^ right

Alternatively, although hiding pointer types in typedef aliases is severely frowned-upon, function pointer types are an exception to that guideline. So, it is easier/clearer to define the required type for the qsort comparator first:

typedef int (*QfnCast) (const void*, const void*);

Then, you can cast to that type:

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

typedef int (*QfnCast) (const void*, const void*);

int main(void)
{
char list[5][8] = {
"Fred",
"Bob",
"Anna",
"Gareth",
"Joe"
};

qsort(list, 5, 8, (QfnCast)(strcmp));
for (int i = 0; i < 5; ++i) printf("%s\n", list[i]);
return 0;
}

Function pointers casting in C++

Converting a void* to a function pointer directly is not allowed (should not compile using any of the casts) in C++98/03. It is conditionally supported in C++0x (an implementation may choose to define the behavior and if it does define it, then it must do what the standard says it should do. A void*, as defined by the C++98/03 standard, was meant to point to objects and not to contain function pointers or member pointers.

Knowing that what you are doing is heavily implementation dependent, here is one option that should compile and work (assuming 32 bit pointers, use long long for 64 bit) on most platforms, even though it is clearly undefined behavior according to the standard:

void *gptr = dlsym(some symbol..) ;
typedef void (*fptr)();
fptr my_fptr = reinterpret_cast<fptr>(reinterpret_cast<long>(gptr)) ;

And here is another option that should compile and work, but carries the same caveats with it as the above:

fptr my_ptr = 0;
reinterpret_cast<void*&>(my_ptr) = gptr;

Or, in Slow motion...

// get the address which is an object pointer
void (**object_ptr)() = &my_ptr;

// convert it to void** which is also an object pointer
void ** ppv = reinterpret_cast<void**>(object_ptr);

// assign the address in the memory cell named by 'gptr'
// to the memory cell that is named by 'my_ptr' which is
// the same memory cell that is pointed to
// by the memory cell that is named by 'ppv'
*ppv = gptr;

It essentially exploits the fact that the address of the function pointer is an object pointer (void (**object_ptr)()) - so we can use reinterpret_cast to convert it to any other object pointer: such as void**. We can then follow the address back (by dereferencing the void**) to the actual function pointer and store the value of the gptr there.

yuk - by no means well-defined code - but it should do what you expect it to do on most implementations.



Related Topics



Leave a reply



Submit