如果余弦是fptr

时间:2017-09-22 19:28:35

标签: 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:“余弦” - > “*” - > “是一个指针” 然后“(”我们走出最里面的()范围 - >“(双)” - >“以取一个双” - >并返回最左边的“双”

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

*(void **) (&cosine)

3 个答案:

答案 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,双指针技巧来欺骗。

另见Casting when using dlsym()

答案 1 :(得分:6)

这是令人讨厌的东西。 cosine是一个指向函数的指针,该函数接受类型double的参数并返回double&cosine是该指针的地址。演员表示假装该地址是指针指向虚空的指针。转换前面的*是通常的解引用运算符,因此结果是告诉编译器假装cosine的类型是void*,以便代码可以分配从dlsymcosine的通话返回值。唷;那伤害了。

而且,只是为了好玩,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来说,两者都是未定义的行为,但后者并不是那么黑暗的魔法。