如果余弦是一个fptr,如何解析*(void**)(&cosine)

bar*_*ney 2 c c++ pointers function-pointers

找到这个代码示例

void *handle;
double (*cosine)(double);
handle = dlopen("libm.so", RTLD_LAZY);
*(void **) (&cosine) = dlsym(handle, "cos");
Run Code Online (Sandbox Code Playgroud)

我使用从右到左阅读规则来解析变量的类型:

double (*cosine)(double);
Run Code Online (Sandbox Code Playgroud)

在这里我从左到右书写但移动LTR:"余弦" - >"*" - >"是指针"然后"("我们走到最里面()范围 - >"(双)" - >"到函数取一个双" - >并返回最左边"双"

但这到底是什么意思?我甚至不知道从哪里开始解析)是"&cosine"的地址或参考?(void**)是什么意思?为什么它最左边的"*"?是取消引用还是类型?

*(void **) (&cosine)
Run Code Online (Sandbox Code Playgroud)

Ste*_*mit 7

是的,那是满口的.

cosine是一个函数指针.所以&cosine是一个指向指针.然后当我们*在它前面拍一个时,我们正在改变原始指针,使其指向其他地方.

它有点像这样:

int i = 5;
int *ip = &i;
*ip = 6;         /* changes i to 6 */
Run Code Online (Sandbox Code Playgroud)

或者它更像是这样的:

char a[10], b[10];
char *p = a;
*(&p) = b;       /* changes p to point to b */
Run Code Online (Sandbox Code Playgroud)

但在你的情况下,它甚至更棘手,因为它cosine是一个指向函数的指针,而不是指向数据的指针.大多数情况下,函数指针指向您在程序中定义的函数.但是在这里,我们正在安排cosine指向由dlsym()函数加载的动态加载函数.

dlsym是超级特殊的,因为它可以返回指向数据的指针,以及指向函数的指针.所以它有一个不可能定义的返回类型.void *当然,它被声明为返回,因为它是C中的"泛型"指针类型.(想想malloc.)但是在纯C中,void *是通用数据指针类型; 它不能保证能够与函数指针一起使用.

直截了当的做法就是说

cosine = dlsym(handle, "cos");
Run Code Online (Sandbox Code Playgroud)

但是现代编译器会抱怨,因为dlsym返回void *,并且cosine有类型double (*)(double)(即,指向函数的指针需要加倍并返回double),这不是可移植的转换.

所以我们绕过谷仓,cosine间接设定价值,而不是说

cosine = something
Run Code Online (Sandbox Code Playgroud)

而是说

*(&cosine) = something
Run Code Online (Sandbox Code Playgroud)

但在这种dlsym情况下仍然没有好处,因为类型仍然不匹配.我们已经void *在右边,所以我们需要void *在左边.并且解决方案是获取地址&cosine,否则它是指向函数的指针,并将其转换为指向指针的指针void,或者void **,当我们*在它前面拍打我们时已经有了一个void *再次,这是分配一个正确的目的地dlsym的返回值.所以我们最终得到了你所问的那条线:

* (void **) (&cosine) = dlsym(handle, "cos");
Run Code Online (Sandbox Code Playgroud)

现在,重要的是要注意我们在这里处于薄弱环节.我们已经使用了&和转换来解决这样一个事实,即指向void指向函数的指针并不严格合法.在这个过程中,我们成功地压制了编译器的警告,即我们正在做的事情并非严格合法.(事实上​​,沉默警告正是原始程序员使用此躲闪的意图.)

潜在的问题是,如果数据指针和函数指针具有不同的大小或表示,该怎么办?这段代码用了一些长度来处理函数指针,cosine好像它是一个数据指针,将数据指针的位干扰到它中.例如,如果数据指针比某个函数指针大,那么这将产生可怕的影响.(并且,在你问"但是数据指针怎么能比函数指针更大?"之后,就像在MS-DOS编程时代的"紧凑"内存模型中那样. )

通常,玩这样的游戏来破坏规则并关闭编译器警告是一个坏主意.dlsym但是,在这种情况下,它很好,我会说完全可以接受. dlsym在函数指针与数据指针不同的系统上不能存在,所以如果你正在使用它dlsym,你必须在一台所有指针都相同的机器上,这个代码才有效.

这也值得一提,如果我们在调用时必须使用强制转换玩游戏dlsym,为什么要用指针指针在barm周围进行额外的旅行呢?为什么不说

cosine = (double (*)(double))dlsym(handle, "cos");
Run Code Online (Sandbox Code Playgroud)

答案是,我不知道.我很确定这个更简单的演员也会同样有效(再次,只要我们在一个dlsym可以存在的系统上).也许有编译器警告这种情况,只能通过使用tricker,双指针技巧来欺骗.

另请参阅使用dlsym()时的转换.


Pet*_*ker 6

这是令人讨厌的东西.cosine是一个指向函数的指针,该函数接受一个类型的参数double并返回double.&cosine是该指针的地址.演员表示假装该地址是指针指向虚空的指针.在*前面的演员中是通常引用操作,所以结果是告诉编译器假装的类型cosinevoid*,这样的代码可以从通话中指定的返回值dlsymcosine.唷; 那很痛.

并且,只是为了好玩,a void*和指向函数的指针完全没有关系,这就是为什么代码必须经历所有的转换.C++语言定义不保证这将起作用.即,结果是未定义的行为.