如何将 dlsym 返回的指针正确赋值给函数指针类型的变量?

How to correctly assign a pointer returned by dlsym into a variable of function pointer type?

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

这是第一个文件。

/* main.c */

#include <dlfcn.h>

int main()
{
    void *handle = dlopen("./foo.so", RTLD_NOW);

    if (handle) {
        void (*func)() = dlsym(handle, "func");
        func();
    }

    return 0;
}

这是第二个文件。

/* foo.c */

#include <stdio.h>

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() 的 return 值类型转换为函数指针,则会收到此警告。

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

什么能让编译器相信这段代码是好的?

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

所以,创建一个函数来包装 dlsym 函数和 returns 一个函数指针。将它放在一个单独的文件中并在没有 -pedantic.

的情况下编译该文件

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

  1. A pointer to void may be converted to or from a pointer to any object type. A pointer to any object type may be converted to a pointer to void and back again; the result shall compare equal to the original pointer.

.

  1. A pointer to a function of one type may be converted to a pointer to a function of another type and back again; the result shall compare equal to the original pointer. If a converted pointer is used to call a function whose type is not compatible with the pointed-to type, the behavior is undefined.

因此函数指针不同于对象指针,因此将 void * 赋值给函数指针总是不严格遵守.

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

  1. A pointer to an object or to void may be cast to a pointer to a function, allowing data to be invoked as a function (6.5.4).
  2. A pointer to a function may be cast to a pointer to an object or to void, allowing a function to be inspected or modified (for example, by a debugger) (6.5.4).

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

/* main.c */

#include <dlfcn.h>

#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 <dlfcn.h>

#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;
}

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

在图书馆

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* 相互转换——而是因为它允许更大的灵活性。

只有编译器"tries to help",所以你必须使用两个类型转换:

#include <inttypes.h>

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

你可以使用 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);

这使我的代码足够迂腐:

*(void**)(&func_ptr) = dlsym(handle, "function_name");

(我在这里找到的http://pubs.opengroup.org/onlinepubs/009695399/functions/dlsym.html