找到此代码示例
void *handle;
double (*cosine)(double);
handle = dlopen("libm.so", RTLD_LAZY);
*(void **) (&cosine) = dlsym(handle, "cos");
我使用从右到左阅读规则来解析变量的类型:
double (*cosine)(double);
这里我从左到右书写但移动LTR:“余弦” - > “*” - > “是一个指针” 然后“(”我们走出最里面的()范围 - >“(双)” - >“以取一个双” - >并返回最左边的“双”
但这到底是什么?我甚至不知道从哪里开始解析)是“&余弦”的地址或参考? (void **)是什么意思?为什么它最左边的“*”?是取消引用还是输入?
*(void **) (&cosine)
答案 0 :(得分:7)
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),这不是便携式转换。
所以我们绕过谷仓,间接设定cosine
的价值,而不是说
cosine = something
而是说
*(&cosine) = something
但在dlsym
案例中仍然没有好处,因为类型仍然不匹配。我们右边有void *
,所以我们左边需要void *
。解决方案是获取地址&cosine
,否则它是指向函数的指针,并将其强制转换为指向指针的指针 - void
或{{ 1}},所以当我们在它前面拍一个void **
时,我们再次得到一个*
,这是指定void *
的返回值的正确目的地。因此,我们最终得到了您所询问的问题:
dlsym
现在,重要的是要注意我们在这里处于薄弱环节。我们已经使用* (void **) (&cosine) = dlsym(handle, "cos");
和强制转换来解决这样一个事实:将指针指向&
到指向函数的指针并不严格合法。在这个过程中,我们成功地压制了编译器的警告,即我们正在做的事情并非严格合法。 (事实上,沉默警告正是原始程序员使用此躲闪的意图。)
潜在的问题是,如果数据指针和函数指针具有不同的大小或表示,该怎么办?这段代码用了一段时间来处理函数指针void
,好像它是一个数据指针,将数据指针的位干扰到它中。例如,如果数据指针比某个函数指针大,那么这将产生可怕的影响。 (并且,在你问“但是数据指针怎么能比函数指针更大?”之后,就像在MS-DOS编程时代的“紧凑”内存模型中那样。 )
通常,玩这样的游戏来破坏规则并关闭编译器警告是一个坏主意。但是在cosine
的情况下,它很好,我会说完全可以接受。函数指针与数据指针不同的系统上不能存在dlsym
,所以如果你正在使用dlsym
,那么你必须在所有指针都相同的机器上,这个代码可以工作。
同样值得一提的是,如果我们在调用dlsym
时必须使用强制转换玩游戏,为什么还要用指针指针在barm周围进行额外的旅行?为什么不直接说
dlsym
答案是,我不知道。我很确定这个更简单的演员也会同样有效(再次,只要我们在一个cosine = (double (*)(double))dlsym(handle, "cos");
可以存在的系统上)。也许有编译器警告这个案例,只能通过使用tricker,双指针技巧来欺骗。
答案 1 :(得分:6)
这是令人讨厌的东西。 cosine
是一个指向函数的指针,该函数接受类型double
的参数并返回double
。 &cosine
是该指针的地址。演员表示假装该地址是指针指向虚空的指针。转换前面的*
是通常的解引用运算符,因此结果是告诉编译器假装cosine
的类型是void*
,以便代码可以分配从dlsym
到cosine
的通话返回值。唷;那伤害了。
而且,只是为了好玩,void*
和指向函数的指针完全没有关系,这就是为什么代码必须经历所有的转换。 C ++语言定义不保证这将起作用。即,结果是未定义的行为。
答案 2 :(得分:2)
对于C,指向void
的指针可以转换为任何指向对象 的指针,而无需转换。但是,C标准不保证void *
可以转换为指向函数的指针 - 完全没有,因为函数不是对象< / em>的
dlsym
是 POSIX 功能;并且POSIX要求作为扩展名,指向函数的指针必须可以转换为void *
并再返回。但是, C ++ 不允许在没有强制转换的情况下进行此类转换。
在任何情况下,*(void **) (&cosine) = dlsym(handle, "cos");
强制转换意味着指向函数(double)返回double 的指针的指针被强制转换为指向void的指针 ,然后取消引用以获得lvalue
类型为void *
的{{1}}的返回值。这是相当丑陋的,并且应该在需要强制转换的地方更好地编写为dlsym
。对于C来说,两者都是未定义的行为,但后者并不是那么黑暗的魔法。