如何正确地将dlsym返回的指针分配给函数指针类型的变量?

我试图在我的代码中使用dlopen()dlsym()并使用gcc编译它。

这是第一个文件。

 /* main.c */ #include  int main() { void *handle = dlopen("./foo.so", RTLD_NOW); if (handle) { void (*func)() = dlsym(handle, "func"); func(); } return 0; } 

这是第二个文件。

 /* foo.c */ #include  void func() { printf("hello, world\n"); } 

这是我编译和运行代码的方法。

 $ gcc -std=c99 -pedantic -Wall -Wextra -shared -fPIC -o foo.so foo.c $ gcc -std=c99 -pedantic -Wall -Wextra -ldl -o main main.c main.c: In function 'main': main.c:10:26: warning: ISO C forbids initialization between function pointer and 'void *' [-Wpedantic] void (*func)() = dlsym(handle, "func"); ^ $ ./main hello, world 

我该如何摆脱警告?

类型转换没有帮助。 如果我尝试将dlsym()的返回值类型转换为函数指针,我会收到此警告。

 main.c:10:26: warning: ISO C forbids conversion of object pointer to function pointer type [-Wpedantic] void (*func)() = (void (*)()) dlsym(handle, "func"); ^ 

什么会说服编译器这个代码没问题?

如果您想要迂腐,请不要尝试解析函数的地址。 相反,从动态库中导出某种结构:

在图书馆

 struct export_vtable { void (*helloworld)(void); }; struct export_vtable exports = { func }; 

在来电者

 struct export_vtable { void (*helloworld)(void); }; int main() { struct export_vtable* imports; void *handle = dlopen("./foo.so", RTLD_NOW); if (handle) { imports = dlsym(handle, "exports"); if (imports) imports->helloworld(); } return 0; } 

这种技术实际上很常见,不是为了便携性 – POSIX保证函数指针可以转换为void * – 但是因为它允许更多的灵活性。

这里的问题是指向对象的指针与函数指针巧妙地分开。 在ISO / IEC 9899: 201x论文§6.3.2.3指针中说明:

  1. 指向void的指针可以转换为指向任何对象类型的指针。 指向任何对象类型的指针可以转换为指向void的指针,然后再返回; 结果应该等于原始指针。

  1. 指向一种类型的函数的指针可以被转换为指向另一种类型的函数的指针并且再次返回; 结果应该等于原始指针。 如果转换的指针用于调用类型与指向类型不兼容的函数,则行为未定义。

因此,函数指针与对象指针不同,因此将void *赋值给函数指针总是严格不符合

无论如何,正如我在评论中所说,在99.9999 …. 9999%的情况下,由于ANNEX J – 可移植性问题,§J.5.7前面提到的论文的函数指针演员表明:

  1. 指向对象或void的指针可以转换为指向函数的指针,允许将数据作为函数调用(6.5.4)。
  2. 指向函数的指针可以转换为指向对象的指针或void,允许检查或修改函数(例如,通过调试器)(6.5.4)。

现在在实践方面,避免在更多文件中拆分代码的技术是使用编译指示来抑制一小段代码的迂腐警告。
更残酷的forms可以是:

 /* main.c */ #include  #pragma GCC diagnostic push //Save actual diagnostics state #pragma GCC diagnostic ignored "-pedantic" //Disable pedantic int main() { void *handle = dlopen("./foo.so", RTLD_NOW); if (handle) { void (*func)() = dlsym(handle, "func"); func(); } return 0; } #pragma GCC diagnostic pop //Restore diagnostics state 

可以启动更复杂的方法来隔离小函数中的违规代码,然后强制其内联。 它更像是化妆而不是有效的解决方案,但会抑制不必要的诊断:

 /* main.c */ #include  #pragma GCC diagnostic push //Save actual diagnostics state #pragma GCC diagnostic ignored "-pedantic" //Disable pedantic void (*)() __attribute__((always_inline)) Assigndlsym(void *handle, char *func) { return dlsym(handle, func); //The non compliant assignment is done here } #pragma GCC diagnostic pop //Restore diagnostics state int main() { void *handle = dlopen("./foo.so", RTLD_NOW); if (handle) { void (*func)() = Assigndlsym(handle, "func"); //Now the assignment is compliant func(); } return 0; } 

要保留代码的-pedantic选项,同时使部分代码不严格符合,请将该代码分成带有自定义警告选项的单独文件。

因此,创建一个包装dlsym函数并返回函数指针的函数。 将它放在一个单独的文件中,并在没有-pedantic情况下编译该文件。

你可以使用union ,就像这样:

 union { void *ptr; void (*init_google_logging) (char* argv0); } orig_func; orig_func.ptr = dlsym (RTLD_NEXT, "_ZN6google17InitGoogleLoggingEPKc"); orig_func.init_google_logging (argv0); 

编译器只“尝试帮助”,所以你必须使用两个类型转换:

 #include  void (*func)() = (void (*)())(intptr_t)dlsym(handle, "func");