Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Casting function pointer to void(*)(), then recasting to original type

This question is for tests purposes, nothing more.

I'm currently trying to store function pointers with a different number of parameters (and these parameters can have different types).

Basically, I've coded the following code snippet in C++11:

#include <functional>
#include <iostream>

void fct(int nb, char c, int nb2, int nb3) {
  std::cout << nb << c << nb2 << nb3 << std::endl;
}

template <typename... Args>
void call(void (*f)(), Args... args) {
  (reinterpret_cast<void(*)(Args...)>(f))(args...);
}

int main(void) {
  call(reinterpret_cast<void(*)()>(&fct), 42, 'c', 19, 94);
}

I convert a void(*)(int, char, int, int) function pointer into a generic void(*)() function pointer. Then, by using variadic template parameters, I simply recast the function pointer to its original type and call the function with some parameters.

This code compiles and runs. Most of the times, it displays the good values. However, this code gives me some Valgrind errors under Mac OS (concerning uninitialized values) and it sometimes displays some unexpected garbage.

==52187== Conditional jump or move depends on uninitialised value(s)
==52187==    at 0x1004E4C3F: _platform_memchr$VARIANT$Haswell (in /usr/lib/system/libsystem_platform.dylib)
==52187==    by 0x1002D8B96: __sfvwrite (in /usr/lib/system/libsystem_c.dylib)
==52187==    by 0x1002D90AA: fwrite (in /usr/lib/system/libsystem_c.dylib)
==52187==    by 0x100025D29: std::__1::__stdoutbuf<char>::overflow(int) (in /usr/lib/libc++.1.dylib)
==52187==    by 0x10001B91C: std::__1::basic_streambuf<char, std::__1::char_traits<char> >::xsputn(char const*, long) (in /usr/lib/libc++.1.dylib)
==52187==    by 0x10003BDB0: std::__1::ostreambuf_iterator<char, std::__1::char_traits<char> > std::__1::__pad_and_output<char, std::__1::char_traits<char> >(std::__1::ostreambuf_iterator<char, std::__1::char_traits<char> >, char const*, char const*, char const*, std::__1::ios_base&, char) (in /usr/lib/libc++.1.dylib)
==52187==    by 0x10003B9A7: std::__1::num_put<char, std::__1::ostreambuf_iterator<char, std::__1::char_traits<char> > >::do_put(std::__1::ostreambuf_iterator<char, std::__1::char_traits<char> >, std::__1::ios_base&, char, long) const (in /usr/lib/libc++.1.dylib)
==52187==    by 0x1000217A4: std::__1::basic_ostream<char, std::__1::char_traits<char> >::operator<<(int) (in /usr/lib/libc++.1.dylib)
==52187==    by 0x1000011E8: fct(int, char, int, int) (in ./a.out)
==52187==    by 0x1000013C2: void call<int, char, int, int>(void (*)(), int, char, int, int) (in ./a.out)
==52187==    by 0x100001257: main (in ./a.out)

I find this quite curious because when I call the function, I have recasted the function pointer to its original type. I thought it was similar to casting a datatype to void* and then recasting it into the original datatype.

What is wrong with my code? Can't we cast function pointers to void(*)() pointer and then recast this pointer to the original function pointer signature?

If not, is there some other ways to achieve this? I'm not interested in std::bind which does not what I want.

like image 538
Simon Ninon Avatar asked Jun 30 '15 22:06

Simon Ninon


People also ask

Can you cast any pointer to a void pointer?

Any pointer to an object, optionally type-qualified, can be converted to void* , keeping the same const or volatile qualifications.

How do I cast a void pointer in C++?

Since we cannot dereference a void pointer, we cannot use *ptr . However, if we convert the void* pointer type to the float* type, we can use the value pointed to by the void pointer. In this example, we have used the static_cast operator to convert the data type of the pointer from void* to float* .

Can you cast function pointers?

Yes, it can. This is purpose of casting function pointers, just like usual pointers. We can cast a function pointer to another function pointer type but cannot call a function using casted pointer if the function pointer is not compatible with the function to be called.


2 Answers

Going out on a limb and guessing what you did to get it to fail...

#include <functional>
#include <iostream>

void fct(int nb, char c, int nb2, std::string nb3) {
  std::cout << nb << c << nb2 << nb3 << std::endl;
}

template <typename... Args>
void call(void (*f)(), Args... args) {
  (reinterpret_cast<void(*)(Args...)>(f))(args...);
}

int main(void) {
  call(reinterpret_cast<void(*)()>(&fct), 42, 'c', 19, "foobar");
}

This will fail because "foobar" never gets converted to std::string ... how can the compiler know if it goes through Args... ?

I'm not sure exactly how std::string gets pushed on the call stack by a caller ( a string reference would be pushed on as a pointer), but I suspect it is more than just a single pointer to char*. When the callee pops off that pointer to char* expecting the entire string member, it freaks out.

I think if you change to

void fct(int nb, char c, int nb2, char* nb3)

or

call(reinterpret_cast<void(*)()>(&fct), 42, 'c', 19, std::string("foobar"));

then it might work.

like image 77
Mark Lakata Avatar answered Sep 24 '22 06:09

Mark Lakata


You said you're also interested in alternative implementations. Personally, I wouldn't implement things this way even if it worked perfectly, both function pointers and reinterpret_casts are things I try to avoid. I haven't tested this code, but my thought would be:

#include <functional>
#include <iostream>
#include <boost/any.hpp>

template <typename... Args>
void call(boost::any clbl, Args... args) {
  auto f = boost::any_cast<std::function<void(Args...)>>(clbl);
  f(args...);
}

int main(void) {
  std::function<void(int, char, int, int)> func = fct;
  call(boost::any(func), 42, 'c', 19, 94);
}

Edit: this code, combined with your definition of fct, works correctly, and runs clean under valgrind on Fedora, compiled with clang35.

like image 22
Nir Friedman Avatar answered Sep 25 '22 06:09

Nir Friedman