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

27

我正在尝试在我的代码中使用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() 的返回值强制转换为函数指针,我会得到这个警告。

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

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


2
不要使用-pedantic警告选项。 - 2501
4
然而,如果我去掉-pedantic选项,可能会失去其他有用的琐碎警告,这些警告可能指出了我的代码中真正存在的问题。为什么建议去掉-pedantic选项? - Lone Learner
7
警告是关于ISO C的,但dlsym()是POSIX的。如果您使用符合POSIX标准的系统,则可以void*转换为函数指针。 - EOF
1
你可以保留-pedantic,但是关闭特定的警告。#pragma GCC diagnostic可以完成这项工作。在其他编译器中,它是#pragma warning(disable: <warn number>) - Frankie_C
2
不要忘记微控制器,其中代码和数据具有不同的“地址空间”!将函数指针转换为void*,然后从中进行memcpy将无法读取代码字节。 - JDługosz
显示剩余4条评论
7个回答

22

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

在库中

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*--而是因为它允许更多的灵活性。


12

2
我不知道为什么之前没有人点赞。这个方法对我很有效,而且比其他建议简单得多。 - msc
1
它违反了“指向数据与函数”的规则,也违反了严格别名规则,因为func_ptr通过不兼容的类型进行修改。 - tstanisl

9
问题在于指向对象的指针与函数指针有微妙的区别。在ISO/IEC 9899:201x文件 §6.3.2.3 Pointers中,它指出:
1. 可以将指向void的指针转换为任何对象类型的指针,也可以将任何对象类型的指针转换为指向void的指针,结果应该等于原始指针。
8. 一个类型为一种函数的指针可以被转换为另一种函数的指针,反之亦然;结果应该等于原始指针。如果转换后的指针用于调用与所指类型不兼容的函数,则行为是未定义的。
因此,函数指针与对象指针不同,因此将 void * 赋值给函数指针始终是非严格符合的。
无论如何,正如我在评论中所说的,在99.9999....9999%的情况下,这是允许的,这要归功于先前提到的文件的附录J - 可移植性问题,§J.5.7 Function pointer casts,其中规定:
1. 可以将对象或void的指针强制转换为函数的指针,使数据作为函数调用(6.5.4)。
2. 一个指向函数的指针可以被转换为对象或void的指针,从而允许检查或修改函数(例如通过调试器)(6.5.4)。
现在,为了避免将代码分割成更多的文件,一种技术是使用编译指示去掉对一小段代码的警告。最直接的形式可能是:
/* 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;
}

1
应该这样写:#pragma GCC diagnostic ignored "-pedantic" //禁用pedantic,不需要加上-w - mjs
1
在gcc 4.6之前不支持#pragma GCC diagnostic push和pop。 - mjs
这行代码 void (*func)() = dlsym(handle, "func"); 给我一个错误 无法使用'rvalue'类型的'void *'初始化类型为'void (*)()'的变量。 - Michael Litvin
@MichaelLitvin 尝试进行类型转换:void (*func)() = (void(*)())dlsym(handle, "func");,如果仍然无法工作并且编译器拒绝分配函数指针,则您处于0.000000....0001%的不兼容情况之一,其中J附录无法应用... - Frankie_C

3
要在代码中保留-pedantic选项,同时又有部分代码不严格符合要求,可以将这部分代码放入一个单独的文件中,并使用自定义警告选项。 因此,创建一个函数来包装dlsym函数并返回函数指针。将其放入单独的文件中,并在编译该文件时不使用-pedantic选项。

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

0

POSIX标准明确规定,你可以“运行”作为指针传递的地址(强调是我的):

Note that conversion from a void * pointer to a function pointer as in:

fptr = (int (*)(int))dlsym(handle, "my_function");

is not defined by the ISO C standard. This standard requires this conversion to work correctly on conforming implementations.

为此,您需要首先“动画化”对象指针。

#define ANIMATED_POINTER(FUNC_TYPE, OBJECT_POINTER) \
    (*((FUNC_TYPE *)(&(OBJECT_POINTER))))

然后您可以直接调用对象变量,

#include <dlfcn.h>

#define ANIMATED_POINTER(FUNC_TYPE, OBJECT_POINTER) \
    (*((FUNC_TYPE *)(&(OBJECT_POINTER))))


typedef void (* MyFuncType) ();


int main () {

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

    if (handle) {

        void * func_obj_ptr = dlsym(handle, "func");
        ANIMATED_POINTER(MyFuncType, func_obj_ptr)();

    }

    return 0;

}

或者你可以将它转换为适当函数类型的另一个变量。

#include <dlfcn.h>

#define ANIMATED_POINTER(FUNC_TYPE, OBJECT_POINTER) \
    (*((FUNC_TYPE *)(&(OBJECT_POINTER))))


typedef void (* MyFuncType) ();


int main () {

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

    if (handle) {

        void * func_obj_ptr = dlsym(handle, "func");
        MyFuncType func = ANIMATED_POINTER(MyFuncType, func_obj_ptr);
        func();

    }

    return 0;

}

加法

尽管从对象指针到函数指针的转换可能会有问题,但在标准C中允许将不同类型的函数指针相互转换。您可以利用这个权限,并将void (*) (void)作为宏的固定目标类型。

#define UNTYPED_ANIMATED_POINTER(OBJECT_POINTER) \
    (*((void (**) (void))(&(OBJECT_POINTER))))

因此,假设有一个仅包含以下内容的addition.c库:
int add_numbers (int first, int second) {
    return first + second;
}

以下是同一段代码的四个变体。 #1
#include <dlfcn.h>
#include <stdio.h>

#define UNTYPED_ANIMATED_POINTER(OBJECT_POINTER) \
    (*((void (**) (void))(&(OBJECT_POINTER))))


int main () {

    void * handle = dlopen("./addition.so", RTLD_NOW);

    if (handle) {

        int (* add_numbers) (int, int);
        void * func_obj_ptr = dlsym(handle, "add_numbers");

        add_numbers = (int (*) (int, int)) UNTYPED_ANIMATED_POINTER(func_obj_ptr);
        int result = add_numbers(19, 23);
        printf("The result is: %d\n", result);  // "The result is: 42"

    }

    return 0;

}

#2

#include <dlfcn.h>
#include <stdio.h>

#define UNTYPED_ANIMATED_POINTER(OBJECT_POINTER) \
    (*((void (**) (void))(&(OBJECT_POINTER))))


int main () {

    void * handle = dlopen("./addition.so", RTLD_NOW);

    if (handle) {

        void * func_obj_ptr = dlsym(handle, "add_numbers");

        int result = ((int (*) (int, int)) UNTYPED_ANIMATED_POINTER(func_obj_ptr))(19, 23);
        printf("The result is: %d\n", result);  // "The result is: 42"

    }

    return 0;

}

#3

#include <dlfcn.h>
#include <stdio.h>

#define UNTYPED_ANIMATED_POINTER(OBJECT_POINTER) \
    (*((void (**) (void))(&(OBJECT_POINTER))))

typedef int (* AdditionFunc) (int, int);


int main () {

    void * handle = dlopen("./addition.so", RTLD_NOW);

    if (handle) {

        void * func_obj_ptr = dlsym(handle, "add_numbers");

        AdditionFunc add_numbers = (AdditionFunc) UNTYPED_ANIMATED_POINTER(func_obj_ptr);
        int result = add_numbers(19, 23);
        printf("The result is: %d\n", result);  // "The result is: 42"

    }

    return 0;

}

#4

#include <dlfcn.h>
#include <stdio.h>

#define UNTYPED_ANIMATED_POINTER(OBJECT_POINTER) \
    (*((void (**) (void))(&(OBJECT_POINTER))))

typedef int (* AdditionFunc) (int, int);


int main () {

    void * handle = dlopen("./addition.so", RTLD_NOW);

    if (handle) {

        void * func_obj_ptr = dlsym(handle, "add_numbers");

        int result = ((AdditionFunc) UNTYPED_ANIMATED_POINTER(func_obj_ptr))(19, 23);
        printf("The result is: %d\n", result);  // "The result is: 42"

    }

    return 0;

}

-3

编译器只是“尽力帮忙”,所以你必须使用两个类型转换:

#include <stdint.h>

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

1
http://en.cppreference.com/w/c/language/cast 表示:“指向函数的指针和整数之间没有转换”。有关详细信息,请参见下面的标准引用。 - Volodymyr Boiko
1
dlsym 的存在证明在相关系统上(很可能是 POSIX,但不一定),你可以(而且必须)将数据指针转换为代码指针。 - Lorinczy Zsigmond

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接