是的,这是一口。
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 * 是通用 data 指针类型;不保证可以和函数指针一起使用。
最直接的做法就是直接说
cosine = dlsym(handle, "cos");
但是现代编译器会抱怨,因为dlsym 返回void *,而cosine 的类型为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()。