如果余弦是 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 个字节)然后我们会“覆盖”内存单元? @barneycosine
是一个函数指针。 &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*
,这样代码就可以将调用的返回值赋给dlsym
到 cosine
。唷;好痛。
而且,只是为了好玩,void*
和指向函数的指针根本不相关,这就是代码必须经过所有这些转换的原因。 C++ 语言定义不保证这将起作用。即,结果是未定义的行为。
【讨论】:
根据 C 和 C++ 标准,dlsym
的使用一直是“未定义行为”。但保证可以在 POSIX 系统上工作。
@usr - 是的。我开始提到它在使用它的系统上可以正常工作,但决定继续贬低它。 “未定义的行为”并不意味着“会发生坏事”。我从未编写过那些东西,但乍一看,我只是将返回值从dlsym
转换为double(*)(double)
。仍然未定义,但更清晰,并且仍然可以在任何地方工作。 对于 C,指向 void
的指针可以转换为 any 指向 object 的指针 无需 强制转换。但是,C 标准确实不保证void *
可以转换为指向函数的指针 - 因为函数不是对象 >.
dlsym
是一个 POSIX 函数;并且 POSIX 要求作为扩展,指向函数的指针必须可转换为 void *
并再次转换回来。但是,C++ 不允许在没有强制转换的情况下进行这种转换。
在任何情况下,*(void **) (&cosine) = dlsym(handle, "cos");
强制转换意味着 指向返回 double 的函数 (double) 的指针的指针 被强制转换为 指向 void 的指针,然后取消引用以获得void *
类型的lvalue
,dlsym
的返回值分配给该lvalue
。这相当难看,在需要演员表的地方最好写成cosine = (double (*)(double))dlsym(handle, "cos")
。当涉及到 C 时,两者都是未定义的行为,但后者没有那么多的黑魔法。
【讨论】:
以上是关于如果余弦是 fptr,如何解析 *(void **) (&cosine)的主要内容,如果未能解决你的问题,请参考以下文章
typedef void far *LPVOID 的具体定义
(C) 使用 while(fscanf(fptr,"%[^\n] %d %f",nome,®Almoxarifado,&preco) != EOF) 将程序锁