找到此代码示例
void *handle;
double (*cosine)(double);
handle = dlopen("libm.so", RTLD_LAZY);
*(void **) (&cosine) = dlsym(handle, "cos");
我使用从右到左的读取规则来解析变量的类型:
double (*cosine)(double);
在这里我从左到右写,但是移动LTR:“余弦”->“*”->“是指针”
然后“(”我们超出最里面的()范围->“(double)”->“要取一个double的函数”->然后返回最左边的“double”
但是这到底是什么?我什至不知道从哪里开始解析)“&cosine”是地址还是引用? (void **)是什么意思?为什么它在外面最左边有“*”?是取消引用还是类型?
*(void **) (&cosine)
最佳答案
是的,这是一口。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 */
但是在您的情况下,它甚至更棘手,因为
cosine
是指向函数的指针,而不是指向数据的指针。大多数情况下,函数指针指向您在程序中定义的函数。但是在这里,我们正在安排使cosine
指向由dlsym()
函数加载的动态加载函数。dlsym
非常特别,因为它可以返回指向数据的指针以及指向函数的指针。因此,它具有一个无法定义的返回类型。当然,它被声明为返回void *
,因为这是C语言中的“通用”指针类型。(请考虑malloc
。)但是在纯C语言中,void *
是通用数据指针类型;它不能保证能够与函数指针一起使用。直接要做的就是说
cosine = dlsym(handle, "cos");
但是现代编译器会抱怨,因为
dlsym
返回void *
,并且cosine
具有double (*)(double)
类型(即,指向函数的指针采用double并返回double的方式),并且这不是可移植的转换。因此,我们绕过谷仓,并间接设置
cosine
的值,而不是说cosine = something
而是说
*(&cosine) = something
但这在
dlsym
情况下仍然没有好处,因为类型仍然不匹配。右侧有void *
,因此左侧需要void *
。解决方案是采用地址&cosine
(否则为指针指向函数的地址),并将其强制转换为指向指针指向void
或void **
的地址,以便在将*
打入在它的前面,我们又得到了一个void *
,这是将dlsym
的返回值分配给的合适目的地。因此,我们最后得到您所询问的那一行:* (void **) (&cosine) = dlsym(handle, "cos");
现在,重要的是要注意我们在这里如履薄冰。我们已经使用
&
和强制转换来解决以下事实:将指向void
的指针分配给“指向函数的指针”并非严格合法。在此过程中,我们已成功消除了编译器的警告,即我们正在做的事情严格不合法。 (实际上,使警告消失只是程序员原本打算使用此躲闪的意图。)潜在的问题是,如果数据指针和函数指针具有不同的大小或表示形式怎么办?此代码经过一定长度的处理,将函数指针
cosine
视为数据指针,从而将数据指针的位塞入其中。例如,如果数据指针比函数指针大一些,那将产生可怕的后果。 (而且,在您问“但是数据指针怎么会比函数指针大?”之前,这正是MS-DOS编程时代的样子,例如,在“紧凑”内存模型中。 )通常,玩这种破坏规则并关闭编译器警告的游戏是一个坏主意。不过,对于
dlsym
来说,这是可以接受的。 dlsym
在功能指针不同于数据指针的系统上不存在,因此,如果您完全使用dlsym
,则必须在所有指针都相同的机器上使用,并且此代码将起作用。值得一提的是,如果我们在调用
dlsym
时是否必须使用强制转换玩游戏,为什么还要使用指针对指针在barm上进行额外的旅行呢?为什么不只是说cosine = (double (*)(double))dlsym(handle, "cos");
答案是,我不知道。我很确定,这种简单的转换也可以正常工作(同样,只要我们使用的系统上完全可以存在
dlsym
即可)。也许有些编译器会警告这种情况,但只能通过使用欺骗性,双指针欺骗来使其静音。另请参见Casting when using dlsym()。