找到此代码示例
void *handle;
double (*cosine)(double);
handle = dlopen("libm.so", RTLD_LAZY);
*(void **) (&cosine) = dlsym(handle, "cos");我使用从右到左的读取规则来解析变量的类型:
double (*cosine)(double);在这里,我从左向右写,但移动LTR:"cosine“”-> "*“->”是一个指针,“那么"(”我们在最里面的()作用域->“( -> )”->“之外运行,函数取一个双”->“,然后返回最左边的”双“。
但这到底是什么?我甚至不知道从哪里开始解析) "&cosine“是一个地址或引用吗?(空**)是什么意思?为什么最左边的"*“在外面?是脱节还是打字?
*(void **) (&cosine)发布于 2017-09-22 19:35:25
是的,那是一口。
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) (即指向函数接受双重和返回双)的指针,这不是可移植的转换。
所以我们绕着谷仓,间接地设定了cosine的价值,而不是说
cosine = something而是说
*(&cosine) = something但是,在dlsym的情况下,这仍然是不好的,因为类型仍然不匹配。我们右边有void *,所以左边需要void *。解决方案是将地址&cosine转换成指针到指针到函数的地址,并将其转换为指针到指针到void或void **,这样当我们将一个*抛到它面前时,我们又得到了一个void *,这是分配dlsym返回值的合适目的地。因此,我们最终得到了您询问的一行:
* (void **) (&cosine) = dlsym(handle, "cos");现在,重要的是要注意,我们在这里处于薄冰之中。我们使用了&和cast来绕过这样一个事实,即将指针到-void分配给“指针到函数”是不合法的。在此过程中,我们成功地沉默了编译器的警告,即我们所做的一切都是不合法的。(实际上,沉默警告恰恰是最初程序员使用这一策略的意图。)
潜在的问题是,如果数据指针和函数指针有不同的大小或表示,怎么办?这段代码用于处理函数指针cosine,就好像它是一个数据指针一样,将数据指针的比特阻塞到其中。如果数据指针在某种程度上比函数指针大,这将产生可怕的影响。(在你问“但是一个数据指针怎么会比一个函数指针大?”之前,例如,在MS编程时代的“紧凑”内存模型中,数据指针就是这样的)。
通常,玩这样的游戏来打破规则并关闭编译器警告是个坏主意。不过,就dlsym而言,这是可以接受的。dlsym不可能存在于函数指针与数据指针不同的系统中,因此,如果您使用的是dlsym,那么您必须在所有指针都相同的机器上运行,这段代码将工作。
同样值得一问的是,如果我们在调用dlsym时必须玩casts游戏,那么为什么还要带指针到指针在谷仓周围多走一圈呢?为什么不直接说
cosine = (double (*)(double))dlsym(handle, "cos");答案是我不知道。我非常肯定,这个简单的转换也会运行得很好(同样,只要我们在一个可以存在dlsym的系统中)。也许有一些编译器会对这种情况发出警告,它们只能通过使用欺骗者双指针的伎俩才能被骗到沉默。
发布于 2017-09-22 19:36:50
这是很恶心的东西。cosine是一个指向函数的指针,该函数接受double类型的参数并返回double。&cosine是该指针的地址。演员们说要假装那个地址是指针到指针到空的地址。强制转换前面的*是通常的取消引用操作符,因此结果是告诉编译器假装cosine的类型是void*,这样代码就可以将调用dlsym的返回值分配给cosine。好痛啊。
而且,只是为了好玩,一个void*和一个指针到函数一点关系都没有,这就是为什么代码必须经过所有的转换。C++语言定义不能保证这是可行的。也就是说,结果是未定义的行为。
发布于 2017-09-22 19:53:42
对于C,可以将指向void的指针转换为没有强制转换的对象的任何指针。但是,C标准并不保证可以将void *转换为指向函数的指针--因为函数不是对象。
dlsym是POSIX函数;POSIX要求作为扩展,指向函数的指针必须可转换到void *并再次返回。但是,C++不允许在没有强制转换的情况下进行这种转换。
无论如何,*(void **) (&cosine) = dlsym(handle, "cos");强制转换意味着指向函数(双)返回双的指针的指针被转换为指向无效指针的指针,然后取消引用以获得void *类型的lvalue,将dlsym的返回值赋给它。这是相当丑陋的,在需要强制转换的地方,应该以cosine = (double (*)(double))dlsym(handle, "cos")的形式编写。当涉及到C时,两者都是未定义的行为,但后者并不是那么黑暗的魔法。
https://stackoverflow.com/questions/46371952
复制相似问题