如果余弦是 fptr,如何解析 *(void **) (&cosine)
How to parse *(void **) (&cosine) if cosine is a fptr
找到此代码示例
void *handle;
double (*cosine)(double);
handle = dlopen("libm.so", RTLD_LAZY);
*(void **) (&cosine) = dlsym(handle, "cos");
我使用从右到左的阅读规则来解析变量的类型:
double (*cosine)(double);
这里我从左到右书写,但移动了 LTR:"cosine" -> "*" -> "is a pointer"
然后 "(" 我们走出最里面的 () 范围 -> "(double)" -> "to function taking one double" -> 并返回最左边的 "double"
但这到底是什么东西?我什至不知道从哪里开始解析)“&cosine”是地址还是引用? (void **) 是什么意思?为什么它最左边的“*”在外面???是解引用还是类型?
*(void **) (&cosine)
是的,那是一口。
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
非常特别,因为它可以 return 指向数据的指针, 以及 指向函数的指针。所以它有一个不可能定义的 return 类型。它被声明为 returning void *
,当然,因为那是 C 中的 "generic" 指针类型。(想想 malloc
。)但是在纯 C 中,void *
是通用的 data 指针类型;它不能保证能够与函数指针一起使用。
最直接的做法就是直接说
cosine = dlsym(handle, "cos");
但是现代编译器会报错,因为dlsym
returns void *
,而cosine
的类型是double (*)(double)
(即指向函数的指针取双倍和 returning 双倍),这不是可移植的转换。
所以我们绕过谷仓,间接设置 cosine
的值,而不是说
cosine = something
而是说
*(&cosine) = something
但是在 dlsym
的情况下这仍然不好,因为类型仍然不匹配。我们在右边有 void *
,所以我们需要在左边有 void *
。解决方案是获取地址 &cosine
,否则它是指向函数指针的指针,并将其转换为指向指向指针的指针void
,或 void **
,所以当我们在它前面打一个 *
时,我们又得到了一个 void *
,这是分配 dlsym
的 return 的正确目的地价值。所以我们以您询问的行结束:
* (void **) (&cosine) = dlsym(handle, "cos");
现在,重要的是要注意我们在这里如履薄冰。我们使用 &
和强制转换来绕过这样一个事实:将指向 void
的指针分配给“指向函数的指针”在严格意义上是不合法的。在这个过程中,我们已经成功地消除了编译器的警告,即我们正在做的事情并不严格合法。 (事实上,消除警告正是程序员使用这种闪避的初衷。)
潜在的问题是,如果数据指针和函数指针具有不同的大小或表示形式怎么办?这段代码花了一些时间来处理函数指针 cosine
,就好像它是一个数据指针一样,将数据指针的位塞入其中。比方说,如果数据指针比函数指针大,这将产生可怕的影响。 (而且,在你问 "But how could a data pointer ever be bigger than a function pointer?" 之前,这正是它们的情况,例如,在 "compact" 内存模型中,回到 MS-DOS 编程时代。)
通常情况下,玩这样的游戏来打破规则并关闭编译器警告是一个坏主意。不过,在 dlsym
的情况下,没关系,我会说完全可以接受。 dlsym
不能存在于函数指针与数据指针不同的系统上,因此如果您使用 dlsym
,您必须在所有指针都相同的机器上,而这代码将起作用。
同样值得一提的是,如果我们在调用 dlsym
时必须使用 cast 来玩游戏,为什么还要用指针对指针绕着 barm 多跑一圈?为什么不直接说
cosine = (double (*)(double))dlsym(handle, "cos");
答案是,我不知道。我很确定这个更简单的转换也能正常工作(同样,只要我们在 dlsym
可以存在的系统上)。也许有些编译器会警告这种情况,只能通过使用骗子、双指针技巧来让这种情况保持沉默。
另见 。
这是讨厌的东西。 cosine
是一个指向函数的指针,该函数接受类型为 double
和 returns double
的参数。 &cosine
是那个指针的地址。演员说假装那个地址是一个指向空指针的指针。 cast前面的*
是通常的解引用运算符,所以结果就是告诉编译器假装cosine
的类型是void*
,这样代码就可以赋值给return 值从对 dlsym
的调用到 cosine
。呸;好痛。
而且,只是为了好玩,void*
和指向函数的指针根本不相关,这就是代码必须经过所有转换的原因。 C++ 语言定义不保证这会起作用。即,结果是未定义的行为。
对于 C,指向 void
的指针可以转换为 any 指向 object 的指针没有演员表。但是,C 标准 不 保证 void *
可以转换为 指向函数 的指针 - 完全没有,因为函数是不是 对象.
dlsym
是一个POSIX函数; POSIX 要求作为扩展,指向函数的指针必须可转换为 void *
并再次返回。但是 C++ 不允许在没有强制转换的情况下进行此类转换。
在任何情况下,*(void **) (&cosine) = dlsym(handle, "cos");
强制转换意味着 指向函数指针的指针 (double) returning double 被强制转换为 指向指向 void 的指针的指针,然后取消引用以获取 void *
类型的 lvalue
,dlsym
的 return 值分配给该 lvalue
.这相当难看,在需要转换的地方最好写成 cosine = (double (*)(double))dlsym(handle, "cos")
。就 C 而言,两者都是未定义的行为,但后者并不是那么黑魔法。
找到此代码示例
void *handle;
double (*cosine)(double);
handle = dlopen("libm.so", RTLD_LAZY);
*(void **) (&cosine) = dlsym(handle, "cos");
我使用从右到左的阅读规则来解析变量的类型:
double (*cosine)(double);
这里我从左到右书写,但移动了 LTR:"cosine" -> "*" -> "is a pointer" 然后 "(" 我们走出最里面的 () 范围 -> "(double)" -> "to function taking one double" -> 并返回最左边的 "double"
但这到底是什么东西?我什至不知道从哪里开始解析)“&cosine”是地址还是引用? (void **) 是什么意思?为什么它最左边的“*”在外面???是解引用还是类型?
*(void **) (&cosine)
是的,那是一口。
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
非常特别,因为它可以 return 指向数据的指针, 以及 指向函数的指针。所以它有一个不可能定义的 return 类型。它被声明为 returning void *
,当然,因为那是 C 中的 "generic" 指针类型。(想想 malloc
。)但是在纯 C 中,void *
是通用的 data 指针类型;它不能保证能够与函数指针一起使用。
最直接的做法就是直接说
cosine = dlsym(handle, "cos");
但是现代编译器会报错,因为dlsym
returns void *
,而cosine
的类型是double (*)(double)
(即指向函数的指针取双倍和 returning 双倍),这不是可移植的转换。
所以我们绕过谷仓,间接设置 cosine
的值,而不是说
cosine = something
而是说
*(&cosine) = something
但是在 dlsym
的情况下这仍然不好,因为类型仍然不匹配。我们在右边有 void *
,所以我们需要在左边有 void *
。解决方案是获取地址 &cosine
,否则它是指向函数指针的指针,并将其转换为指向指向指针的指针void
,或 void **
,所以当我们在它前面打一个 *
时,我们又得到了一个 void *
,这是分配 dlsym
的 return 的正确目的地价值。所以我们以您询问的行结束:
* (void **) (&cosine) = dlsym(handle, "cos");
现在,重要的是要注意我们在这里如履薄冰。我们使用 &
和强制转换来绕过这样一个事实:将指向 void
的指针分配给“指向函数的指针”在严格意义上是不合法的。在这个过程中,我们已经成功地消除了编译器的警告,即我们正在做的事情并不严格合法。 (事实上,消除警告正是程序员使用这种闪避的初衷。)
潜在的问题是,如果数据指针和函数指针具有不同的大小或表示形式怎么办?这段代码花了一些时间来处理函数指针 cosine
,就好像它是一个数据指针一样,将数据指针的位塞入其中。比方说,如果数据指针比函数指针大,这将产生可怕的影响。 (而且,在你问 "But how could a data pointer ever be bigger than a function pointer?" 之前,这正是它们的情况,例如,在 "compact" 内存模型中,回到 MS-DOS 编程时代。)
通常情况下,玩这样的游戏来打破规则并关闭编译器警告是一个坏主意。不过,在 dlsym
的情况下,没关系,我会说完全可以接受。 dlsym
不能存在于函数指针与数据指针不同的系统上,因此如果您使用 dlsym
,您必须在所有指针都相同的机器上,而这代码将起作用。
同样值得一提的是,如果我们在调用 dlsym
时必须使用 cast 来玩游戏,为什么还要用指针对指针绕着 barm 多跑一圈?为什么不直接说
cosine = (double (*)(double))dlsym(handle, "cos");
答案是,我不知道。我很确定这个更简单的转换也能正常工作(同样,只要我们在 dlsym
可以存在的系统上)。也许有些编译器会警告这种情况,只能通过使用骗子、双指针技巧来让这种情况保持沉默。
另见
这是讨厌的东西。 cosine
是一个指向函数的指针,该函数接受类型为 double
和 returns double
的参数。 &cosine
是那个指针的地址。演员说假装那个地址是一个指向空指针的指针。 cast前面的*
是通常的解引用运算符,所以结果就是告诉编译器假装cosine
的类型是void*
,这样代码就可以赋值给return 值从对 dlsym
的调用到 cosine
。呸;好痛。
而且,只是为了好玩,void*
和指向函数的指针根本不相关,这就是代码必须经过所有转换的原因。 C++ 语言定义不保证这会起作用。即,结果是未定义的行为。
对于 C,指向 void
的指针可以转换为 any 指向 object 的指针没有演员表。但是,C 标准 不 保证 void *
可以转换为 指向函数 的指针 - 完全没有,因为函数是不是 对象.
dlsym
是一个POSIX函数; POSIX 要求作为扩展,指向函数的指针必须可转换为 void *
并再次返回。但是 C++ 不允许在没有强制转换的情况下进行此类转换。
在任何情况下,*(void **) (&cosine) = dlsym(handle, "cos");
强制转换意味着 指向函数指针的指针 (double) returning double 被强制转换为 指向指向 void 的指针的指针,然后取消引用以获取 void *
类型的 lvalue
,dlsym
的 return 值分配给该 lvalue
.这相当难看,在需要转换的地方最好写成 cosine = (double (*)(double))dlsym(handle, "cos")
。就 C 而言,两者都是未定义的行为,但后者并不是那么黑魔法。