C++ 如果余弦是fptr,如何解析*(void**)(&;cosine)

C++ 如果余弦是fptr,如何解析*(void**)(&;cosine),c++,c,pointers,function-pointers,C++,C,Pointers,Function Pointers,找到这个代码示例 void *handle; double (*cosine)(double); handle = dlopen("libm.so", RTLD_LAZY); *(void **) (&cosine) = dlsym(handle, "cos"); 我使用从右向左的读取规则来解析变量的类型: double (*cosine)(double); 这里我从左向右写,但移动LTR:“余弦”->“*”->“是指针” 然后“(“我们在最里面的()范围之外->”(double)“

找到这个代码示例

void *handle;
double (*cosine)(double);
handle = dlopen("libm.so", RTLD_LAZY);
*(void **) (&cosine) = dlsym(handle, "cos");
我使用从右向左的读取规则来解析变量的类型:

double (*cosine)(double);
这里我从左向右写,但移动LTR:“余弦”->“*”->“是指针” 然后“(“我们在最里面的()范围之外->”(double)“->”函数取一个double->并返回最左边的“double”

但这到底是什么?我甚至不知道从哪里开始解析“&余弦”是地址还是引用?(空白**)是什么意思?为什么外面有最左边的“*”???是取消引用还是键入

*(void **) (&cosine)

是的,那是一口

cosine
是一个函数指针。因此,
&余弦
是指向该指针的指针。然后当我们在它前面拍一个
*
时,我们改变了原始指针,使它指向其他地方

有点像这样:

int i = 5;
int *ip = &i;
*ip = 6;         /* changes i to 6 */
char a[10], b[10];
char *p = a;
*(&p) = b;       /* changes p to point to b */
或者更像这样:

int i = 5;
int *ip = &i;
*ip = 6;         /* changes i to 6 */
char a[10], b[10];
char *p = a;
*(&p) = b;       /* changes p to point to b */
但在您的例子中,它甚至更为棘手,因为
余弦
是指向函数的指针,而不是指向数据的指针。大多数情况下,函数指针指向您在程序中定义的函数。但是在这里,我们安排让
cosine
指向一个动态加载的函数,由
dlsym()
函数加载

dlsym
非常特殊,因为它可以返回指向数据的指针以及指向函数的指针。因此,不可能定义返回类型。当然,它被声明为返回
void*
,因为这是C中的“通用”指针类型(想想
malloc
),但在纯C中,
void*
是通用的数据指针类型;它不能保证能够与函数指针一起使用

最简单的方法就是说

cosine = dlsym(handle, "cos");
但现代编译器会抱怨,因为
dlsym
返回
void*
,而
cosine
具有类型
double(*)(double)
(即指向函数取双精度并返回双精度的指针),这不是可移植的转换

所以我们绕着谷仓转,间接地设置余弦的值,而不是说

cosine = something
*(&cosine) = something
而是说

cosine = something
*(&cosine) = something
但是在
dlsym
的情况下,这仍然是不好的,因为类型仍然不匹配。右边有
void*
,所以左边需要
void*
。解决方法是取地址
&余弦
,它是指向函数的指针,然后将它转换为指向-
void
,或
void**
,这样当我们在它前面打一个
*
时,我们又得到了一个
void*
,哪个是分配
dlsym
返回值的正确目标。因此,我们以您询问的那句话结束:

* (void **) (&cosine) = dlsym(handle, "cos");
现在,重要的是要注意,我们在这里是在薄冰上。我们使用了
&
和cast来回避这样一个事实:将指向-
void
的指针指定给`指向函数的指针'并不是严格合法的。在这个过程中,我们成功地消除了编译器关于我们所做的不完全合法的警告。(事实上,让警告静音正是程序员使用这种闪避的初衷。)

潜在的问题是,如果数据指针和函数指针具有不同的大小或表示,该怎么办?这段代码经过一定长度,将函数指针余弦当作数据指针,将数据指针的位塞入其中。比如说,如果数据指针比函数指针大,这将产生可怕的影响。(在你问“但是数据指针怎么可能比函数指针大呢?”之前,这正是在MS-DOS编程时代,例如在“紧凑”内存模型中的情况。)

通常,玩这样的游戏来打破规则并关闭编译器警告是个坏主意。但是,对于
dlsym
,我认为这是完全可以接受的
dlsym
不能存在于函数指针与数据指针不同的系统上,因此如果您使用的是
dlsym
,则您必须位于所有指针都相同的机器上,并且此代码可以工作

同样值得一问的是,如果我们必须在调用
dlsym
时使用演员扮演游戏,为什么还要用指针指向指针在酒吧周围进行额外的旅行呢?为什么不直接说呢

cosine = (double (*)(double))dlsym(handle, "cos");
答案是,我不知道。我敢肯定,这个简单的cast也同样有效(同样,只要我们在一个
dlsym
可以存在的系统上)。也许有编译器警告这种情况,只有使用欺骗者的双指针技巧才能使其保持沉默


另请参见。

这是令人讨厌的东西
cosine
是指向函数的指针,该函数接受类型为
double
的参数并返回
double
&余弦
是该指针的地址。演员说假装那个地址是指向void的指针。cast前面的
*
是常用的解引用操作符,因此结果是告诉编译器假装
余弦的类型是
void*
,这样代码就可以将调用的返回值分配给
dlsym
余弦。呸;那很痛


而且,只是为了好玩,一个
void*
和一个指向函数的指针根本不相关,这就是为什么代码必须经过所有的转换。C++语言定义不能保证这能起作用。也就是说,结果是未定义的行为。

对于C,指向
void
的指针可以转换为指向对象的任何指针,而无需强制转换。但是,C标准不能保证
void*
可以转换为指向函数的指针,因为函数不是对象

dlsym
是一个POSIX函数;POSIX要求,作为扩展,指向函数的指针必须可转换为<代码