C++中的函数指针类型转换

53

我有一个由dlsym()返回的void指针,我想调用由该指针指向的函数。 因此,我通过强制类型转换进行了类型转换:

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

我也尝试过 reinterpret_cast,但没有成功,尽管 C 语言风格的强制类型转换符似乎可以工作。


我认为将函数指针转换为void*最初是个坏主意。从dlsym返回函数指针是否存在问题? - Kirill V. Lyadvinsky
8个回答

64

在C++98/03标准下,直接将void*转换为函数指针是不允许的(任何类型的强制转换都不应该编译)。按条件,在C++0x中支持它(实现可以选择定义行为,如果定义了行为,则必须执行标准规定的操作)。根据C++98/03标准,void*应该指向对象而不是包含函数指针或成员指针。

明确这种依赖于具体实现的做法可能会有所帮助。以下代码应该能够在大多数平台上编译和工作(假设32位指针,对于64位请使用long long),即使根据标准显然属于未定义的行为:

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

这里还有另一种选项,应该可以编译并运行,但和上面的方法存在相同的注意事项:

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

或者,慢动作中...

// 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;  

这个方法本质上利用了函数指针的地址是一个对象指针(void (**object_ptr)())这一事实 - 因此我们可以使用reinterpret_cast将其转换为任何其他对象指针,例如void**。然后我们可以通过对void**进行解引用来跟踪地址返回实际的函数指针,并在那里存储gptr的值。

yuk- 这并不是严格定义的代码 - 但它应该在大多数实现中做你所期望的事情。


1
我认为这就是它的全部 - C++转换符合标准,C转换符合POSIX共享库调用的要求。 - Daniel Earwicker
即使不需要C风格的显式转换来进行转换或编译 - C风格的转换是根据其他转换定义的(还有一个关于基类可访问性的小附加功能)。 - Faisal Vali
8
顺便提一下,在进行中间转换时更好的选择是使用 size_t 类型 - 在任何平台上通常足够大,可以容纳一个指针,尽管这也不能保证。最好还是在可用的情况下(C99、C++TR1、C++0x),使用 <stdint.h>/<cstdint> 头文件和其中的 intptr_t 类型定义。 - Pavel Minaev
3
“条件支持”的措辞实际上是考虑了dlsym()的行为而发明的——大约在2001年,人们注意到POSIXy系统上的真正的C++编译器都接受这种转换。 - MSalters
2
@MSalters - 謝謝您提供的背景資料 :) - 這是可能開啟一切的DR連結:http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#195 - Faisal Vali
1
如果您想要更具可移植性的方式,您可以在C语言中编写一个特定的dlsym包装器,该包装器返回函数指针,并从C++中调用该包装器。 - user1203803


2

如果您了解参数列表是什么,那么将其强制转换为C语言类型非常简单。如上所述,这样做会导致未定义的行为,但我在自己的宠物项目中使用它作为事件处理程序已经运行良好,似乎在MSVC上也没有任何问题。

我可以将同一个void*强制转换为_beginthread_proc_type以使用_beginthread()启动线程,这似乎也不会引起任何问题(尽管我不知道向不需要参数的函数发送参数或者不向需要参数的函数发送参数的后果是什么,但在我的有限测试中它至少调用了函数/启动了线程):

void somefunction(){
    std::cout <<"hi"<<std::endl;
}

void* function = (void*)&somefunction;
((void(__cdecl*)(void))(function)) ();

_beginthread((_beginthread_proc_type)function, 0, NULL);

我知道社区普遍对宏有厌恶情绪,但是在我的事件处理程序中,我用了一个宏来调用那个函数:

#define call_voidstar_function(fc)     ((void(__cdecl*)(void))(fc)) ()

1
我找到了这个(有点丑陋的)解决方案。 使用最高警告级别的gcc不会抱怨。 此示例调用dlsym()(返回void*),并将结果返回到函数指针中。
typedef void (*FUNPTR)();

FUNPTR fun_dlsym(void* handle, const char* name) {
    union {
        void* ptr;
        FUNPTR fptr;
    } u;
    u.ptr = dlsym(handle, name);
    return u.fptr;
}

1
如果编译单元是C,那么这个方法可以工作,但是对于C++11及更高版本,它是未定义的行为:https://dev59.com/9mgu5IYBdhLWcg3wYF-3 - Vitali

1

这个在Visual Studio中编译时不需要使用reinterpret_cast:

void *ptr;
int (*func)(void) = (int(*)(void))ptr;
int num = func();

3
可能会编译通过,但会导致未定义的行为(正如C规范所描述的)。 - luis.espinal
2
真的没有 reinterpret_cast 吗?编译器会选择哪种转换方式? - Janusz Lenar
10
你正在执行C风格的转换,这在这种情况下实际上是一种重新解释的转换。 - WestleyArgentum

0
一个可能会使用以下技巧:
int (*fn)(int);
*(void **)(&fn) = dlsym(lib1, "function");
int result = (*fn)(3);

或者

fn = (int (*)(int))dlsym(lib1, "function");

编译使用:

g++ -Wall -pedantic -std=c++11

0
你可以将 dlsym 转换为返回所需指针的函数,然后像这样调用它:
typedef void (*fptr)();
fptr my_fptr = reinterpret_cast<fptr (*)(void*, const char*)>(dlsym)(RTLD_DEFAULT, name);

PS. 将函数指针转换为不同的函数指针,然后调用它是未定义的行为(请参见https://en.cppreference.com/w/cpp/language/reinterpret_cast中的第7点),因此最好将dlsym的结果转换为uintptr_t,然后再转换为所需的类型:

fptr my_fptr = reinterpret_cast<fptr>(reinterpret_cast<uintptr_t>(dlsym(RTLD_DEFAULT, name)));

-3
这可能会对你有所帮助。它会打印出“Hello”。
#include <iostream>

void hello()
{
  std::cout << "Hello" << std::endl;
}

int main() {
  typedef void (*fptr)();
  fptr gptr = (fptr) (void *) &hello;
  gptr();
}

或者你可以这样做:

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

其中 &hello 被 dlsym 命令替换。


2
如果那有帮助的话,我会感到非常惊讶! - Daniel Earwicker
这个代码之所以能够工作,是因为你没有使用 void * 指针。 - Daniel Earwicker
1
修改后没问题了吧?代码似乎可以运行。(虽然我不是专家,所以也许这样做可以工作,但实际上是未定义的?) - Mike

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接