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

Posted

技术标签:

【中文标题】如果余弦是 fptr,如何解析 *(void **) (&cosine)【英文标题】:How to parse *(void **) (&cosine) if cosine is a fptr 【发布时间】:2017-09-22 19:28:35 【问题描述】:

找到这个代码示例

void *handle;
double (*cosine)(double);
handle = dlopen("libm.so", RTLD_LAZY);
*(void **) (&cosine) = dlsym(handle, "cos");

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

double (*cosine)(double);

这里我从左到右写,但移动 LTR:“余弦”->“*”->“是一个指针” 然后 "(" 我们走出最里面的 () 范围 -> "(double)" -> "to function take one double" -> 并返回最左边的 "double"

但这到底是什么?我什至不知道从哪里开始解析)“&cosine”是地址还是引用? (void **) 是什么意思?为什么它在外面有最左边的“*”???是取消引用还是类型?

*(void **) (&cosine)

【问题讨论】:

这只是一个演员表。您不能将该规则用于表达式,只能用于声明。 @Rakete1111 好的,但要转换成什么? ) 在余弦之前是 & 的“地址”吗? 是的。这是void**的演员表。 Eeek;讨厌的 C 风格演员表。一个很好的例子,说明为什么你应该总是使用更安全、更明确/可读的 C++ 强制转换.. 【参考方案1】:

是的,这是一口。

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()。

【讨论】:

wow ) 所以,当我们将余弦变量的地址转换为这个指向空指针的指针时,我们有一个“普通的”“数据”指针,对吧?我的意思是这个指向变量(它是一个 fpt)的“高阶”指针是否会保留其保证的(void *)大小?或者指向 fptr (&cosine) 的强类型指针将保持“函数指针大小”?在 16 位 DOS 编译器中究竟会在哪里发生(理论上的)数据丢失?当我们通过地址分配一个 void *(比如 4 个字节)到函数 ptr 的位置(比如段:偏移 - 6 个字节)然后我们会“覆盖”内存单元? @barney cosine 是一个函数指针。 &cosine 是一个数据指针(它指向变量cosine)。 (void **)(&cosine) 是一个稍微不同的数据指针,它指向存储cosine 的内存,但将指向的内存视为不同的类型——void *,而不是函数指针。因此,当我们说*(void **)(&cosine) = something; 时,它的位在哪里,就好像它是void *,这很好如果 void * 的大小和表示形式与函数指针完全相同. 我的意思是这个指向变量(它是一个 fpt)的“高阶”指针是否会保持其保证的 (void*) 大小? 指针 (void **)(&cosine ) 不是函数指针。它是一个指向指针的指针(我猜这就是你所说的“高阶”),但外部的“高阶”指针指向的是void *,而不是函数指针。所以是的,当我们“通过地址分配一个 void * 到函数 ptr 的位置”时,我们可能会造成损害。 *(void **) 的扭曲在 dlsym() 的一些手册页中列出。实际上,编译器接受从 void * 到函数指针的显式转换(与需要转换的数据指针不同)并且是更可取的形式,因为不能保证 void * 具有与函数指针。我不知道任何编译器曾经发出过虚假警告 - 所以怀疑“在谷仓里转悠”(可爱的描述!)是由于某人太聪明或偏执而导致的【参考方案2】:

这是讨厌的东西。 cosine 是一个指向函数的指针,该函数接受 double 类型的参数并返回 double&cosine 是该指针的地址。演员说假装该地址是一个指向空指针的指针。类型转换前面的* 是通常的解引用运算符,所以结果是告诉编译器假装cosine 的类型是void*,这样代码就可以将调用的返回值赋给dlsymcosine。唷;好痛。

而且,只是为了好玩,void* 和指向函数的指针根本不相关,这就是代码必须经过所有这些转换的原因。 C++ 语言定义不保证这将起作用。即,结果是未定义的行为。

【讨论】:

根据 C 和 C++ 标准,dlsym 的使用一直是“未定义行为”。但保证可以在 POSIX 系统上工作。 @usr - 是的。我开始提到它在使用它的系统上可以正常工作,但决定继续贬低它。 “未定义的行为”并不意味着“会发生坏事”。我从未编写过那些东西,但乍一看,我只是将返回值从dlsym 转换为double(*)(double)。仍然未定义,但更清晰,并且仍然可以在任何地方工作。 是的,据我所知“指向函数的指针”甚至不能保证与 void * 大小相同【参考方案3】:

对于 C,指向 void 的指针可以转换为 any 指向 object 的指针 无需 强制转换。但是,C 标准确实保证void * 可以转换为指向函数的指针 - 因为函数不是对象 >.

dlsym 是一个 POSIX 函数;并且 POSIX 要求作为扩展,指向函数的指针必须可转换为 void * 并再次转换回来。但是,C++ 不允许在没有强制转换的情况下进行这种转换。

在任何情况下,*(void **) (&cosine) = dlsym(handle, "cos"); 强制转换意味着 指向返回 double 的函数 (double) 的指针的指针 被强制转换为 指向 void 的指针,然后取消引用以获得void * 类型的lvaluedlsym 的返回值分配给该lvalue。这相当难看,在需要演员表的地方最好写成cosine = (double (*)(double))dlsym(handle, "cos")。当涉及到 C 时,两者都是未定义的行为,但后者没有那么多的黑魔法。

【讨论】:

以上是关于如果余弦是 fptr,如何解析 *(void **) (&cosine)的主要内容,如果未能解决你的问题,请参考以下文章

如何在函数中动态分配内存?

typedef void far *LPVOID 的具体定义

java算法---余弦相似度计算字符串相似率

Char 和 Int 打印两个不同的值

无法挂钩回调函数?

(C) 使用 while(fscanf(fptr,"%[^\n] %d %f",nome,&regAlmoxarifado,&preco) != EOF) 将程序锁