4 回答
首先,g++不将链接视为类型的一部分。你不so.hpp
应该编译,因为它试图用 初始化一个myclass* (*)()
(指向extern "C"
函数的指针)make
,这是一个extern
"C"
函数。这是非法的,并且需要编译器错误,但 g++ 接受它甚至没有警告。
除此之外,如果未定义,为什么要host.cpp
生成警告。usefunction
在 DLL 中,interface
是包含指向函数的指针的数据类型的实例。您用于dlsym
获取此变量(不是函数)的地址,并将其转换为数据类型。永远不要将指向数据的指针转换为指向函数的指针;您取消引用指向包含指向函数的指针的数据对象的指针,这很好。
至于带有. reinterpret_cast
_ 标准(至少通过 C++03)说这种转换是非法的,并且我使用了无法使其工作的编译器,因为指向函数的指针大于指向数据的指针。作为在 C 中允许的限制,Unix (Posix) 要求指向函数的指针和指向数据的指针具有相同的大小和表示形式,并且 Posix 标准规定将返回值转换如下:dlsym
void*
dlsym
myclass* (*func)();
*reinterpret_cast<void**>( &func ) = dlsym( th, "make" );
如果 amyclass* (*)()
和 avoid*
实际上具有相同的大小和表示,这是合法的,并且会起作用(并且不应触发任何警告)。
gcc 的警告是因为 gcc 不知道您是否愿意假设 Posix。所以它假设不是并且(根据 C++ 标准的要求)它诊断出格式错误的程序。
但是,您正在使用dlsym
并期望它完成 Posixdlsym
所做的事情,因此您愿意依赖 Posix。然后你可以对你的函数指针类型进行 C 风格的void*
转换,并且 gcc 保证这是可以的,即使 C++ 没有。任何模仿的非 Posix 系统dlsym
都必须保证类似的东西,否则在 a 中返回函数指针是无意义的void*
。
既然您知道自己在做什么,那么您就可以使来自 gcc 的任何警告静音。
具有结构的代码API
没有给出任何警告的原因是它void*
可以static_cast
指向任何指向对象的指针。我认为您在访问数据成员时违反了严格的别名,因为API
当该位置的实际对象是void*
. void*
但是因为您的结构的布局与您的实现中的 a 和指向函数的指针的布局相同,所以它无论如何都可以工作。从理论上讲,即使使用相同的布局,它也可能因为严格的混叠违规而中断(您使用的优化越多)。
避免严格混叠违规的安全方法是:与Kerrekstd::memcpy(&fp, &p, sizeof p)
的相同,但由于需要一个完整的类型,因此需要std::copy
较少的reinterpret_casts
混乱。除了避免严格的混叠违规外,这还方便地避免了任何诊断。您不再在函数和对象指针之间进行转换:您直接将一个对象表示复制到另一个。只要保证 的对象表示与指向函数的指针相同(在 Posix 中就是这种情况),这将起作用。memcpy
void*
std::copy
void*
完全符合标准的解决方案:
extern "C" typedef int (func_t)(char, double); // desired API function signature
int main()
{
static_assert(sizeof(void *) == sizeof(func_t *), "pointer cast impossible");
void * p = dlsym(handle, "magic_function");
char const * cp = reinterpret_cast<char const *>(&p);
func_t * fp;
std::copy(cp, cp + sizeof p, reinterpret_cast<char *>(&fp));
return fp('a', 1.25);
}
一种更简单但更可疑的写法是使用一些类型双关语:
static_assert(sizeof(void *) == sizeof(func_t *), "pointer cast impossible");
void * vp = dlsym(handle, "magic_function");
func_t * fp;
*reinterpret_cast<void **>(&fp) = vp; // this is type-punning
如果您将函数指针包装在结构中,那么您可以通过添加额外的间接级别来解决问题。想象一下,如果一个函数指针占用 10 个字节,而一个对象指针占用 4 个字节。您的结构至少有 10 个字节,并且您将有一个 4 字节的指针。这一切都很好。当您访问该结构以获取您的函数指针时,您提取了完整的 10 个字节。什么都没有丢失。但是,如果要将 10 字节的函数指针转换为 4 字节的对象指针,则必然会丢失 6 字节的信息。
这不是一个实际的问题,因为支持 dlsym 的平台必须有足够大的 void 指针来存储函数指针的地址,但是编译器试图阻止您编写不可移植的代码。