【问题标题】:Casting function pointer to void(*)(), then recasting to original type将函数指针转换为 void(*)(),然后重新转换为原始类型
【发布时间】:2015-09-17 21:46:54
【问题描述】:

这个问题是为了测试目的,仅此而已。

我目前正在尝试存储具有不同数量参数的函数指针(这些参数可以有不同的类型)。

基本上,我在 C++11 中编写了以下代码 sn-p:

#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);
}

我将void(*)(int, char, int, int) 函数指针转换为通用void(*)() 函数指针。然后,通过使用可变参数模板参数,我简单地将函数指针重新转换为其原始类型并使用一些参数调用函数。

此代码编译并运行。大多数时候,它显示了良好的价值。但是,这段代码在 Mac OS 下给了我一些 Valgrind 错误(关于未初始化的值),并且有时会显示一些意外的垃圾。

==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)

我觉得这很好奇,因为当我调用该函数时,我已将函数指针重铸为其原始类型。我认为这类似于将数据类型转换为void*,然后将其重新转换为原始数据类型。

我的代码有什么问题?难道我们不能将函数指针转换为void(*)()指针,然后将此指针重新转换为原始函数指针签名吗?

如果没有,还有其他方法可以实现吗?我对std::bind 不感兴趣,这不是我想要的。

【问题讨论】:

  • 你的意思是函数指针?不是指向成员函数的指针?
  • 我无法重现。那是您实际的文字测试用例吗?
  • 是的,这正是我运行的代码(在 macos Yosemite 上)。此外,如果我用 std::string 替换最后一个参数,我得到了垃圾。
  • [expr.reinterpret.cast] 说你可以重新解释一个函数指针,然后再返回并取回原始值。也许你有编译器错误?
  • 您应该发布失败的代码。如果您将"foobar" 作为参数传递给std::string,那么这就解释了问题所在。 call() 不知道它必须将 "foobar" 转换为 std::string,因为没有函数原型。

标签: c++ pointers c++11 function-pointers


【解决方案1】:

四处走动并猜测你做了什么让它失败......

#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");
}

这将失败,因为“foobar”永远不会转换为std::string ...编译器如何知道它是否通过Args...

我不确定 std::string 是如何被调用者推送到调用堆栈上的(字符串引用将作为指针推送),但我怀疑它不仅仅是指向 char* 的单个指针.当被调用者弹出指向 char* 并期待整个 string 成员的指针时,它吓坏了。

我想如果你换成

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

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

那么它可能工作。

【讨论】:

  • 合乎逻辑!谢谢你的解释。看看如何处理可变参数列表可能是个好主意。我认为我得到的 valgrind 错误是由于 macOSX 上的 valgrind 实现不稳定造成的。
【解决方案2】:

您说您也对替代实现感兴趣。就个人而言,即使它工作得很好,我也不会以这种方式实现,函数指针和 reinterpret_casts 都是我试图避免的事情。我没有测试过这段代码,但我的想法是:

#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);
}

编辑:此代码与您对 fct 的定义相结合,可以正常工作,并且在 Fedora 上的 valgrind 下运行干净,使用 clang35 编译。

【讨论】:

  • 代码更清晰,但我认为这只会隐藏 reinterpret_cast (这肯定是在 boost::any_cast 实现内部完成的)。但是,谢谢你的例子,直到现在我还没有听说过 boost::any 和 boost::any_cast!
  • 内部其实是一个static_cast。甚至还有 boost::any 的变体,它们在内部使用动态转换......如果你搞砸了,很高兴得到一个异常而不是段错误。
猜你喜欢
  • 1970-01-01
  • 2021-09-20
  • 1970-01-01
  • 1970-01-01
  • 2017-10-07
  • 2012-11-21
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多