使用 dlsym() 时进行转换

Casting when using dlsym()

我在 C 中使用 dlsym(),我想知道 dlsym() 的 return 值是应该显式转换还是正确隐式转换.这是函数:

double (*(compile)(void))(double x, double y)
{
    if (system("scan-build clang -fPIC -shared -g -Wall -Werror -pedantic "
           "-std=c11 -O0 -lm foo.c -o foo.so") != 0) {
        exit(EXIT_FAILURE);
    }

    void *handle;
    handle = dlopen("./foo.so", RTLD_LAZY);
    if (!handle) {
        printf("Failed to load foo.so: %s\n", dlerror());
        exit(EXIT_FAILURE);
    }

    foo f;
    f = (foo)dlsym(handle, "foo");
    if (!f) {
        printf("Failed to find symbol foo in foo.so: %s\n", dlerror());
        exit(EXIT_FAILURE);
    }
    return f;
}

函数 compile() 不接受值,return 是一个指向函数的指针,该函数接受两个 double 作为输入并且 return 是一个双精度值。然后我设置了一个系统调用来编译一个共享的 object foo.so。然后我用 dlopen() 打开 foo.so。然后 dlsym()foo.so 中找到 foo 并且 return 是一个类型为 foo 的 object,我在 header 中将其定义为:

typedef double (*foo)(double, double);

我必须施放 dlsym() 吗?

编写 C 标准是为了假定指向不同对象类型的指针,尤其是指向与对象类型相反的函数的指针,可能具有不同的表示形式。这就是为什么一般来说,您不想混合使用指针,或者如果您这样做,现代编译器会警告您,如果您想消除警告,您通常会使用显式强制转换。

dlsym,另一方面,它的存在假设所有指针都几乎相同,因为它应该能够 return 你指向任何数据类型的任何指针 -对象 函数 -- 在您的对象文件中。

换句话说,使用 dlsym 的代码本质上是不可移植的,因为它不能广泛移植,因为它可以移植 "only" 到所有指针都可以安全相互转换的机器. (这当然是当今几乎所有流行的机器。)

所以,是的,您需要转换指针以使警告静音,这样做可能会使您的代码难以移植到所有指针都不相同的机器(以及警告,如果取消静音,会正确地通知您您的代码将无法工作),但是 dlsym 永远不会在那些机器上工作(甚至以其当前形式存在),无论如何。

(如果 gcc -pedantic 警告您甚至从 void * 到函数指针类型的显式转换,除了切换到 gcc 的不同版本之外您无能为力, 或者当然不使用 -pedantic.)


附录:我的回答听起来好像在指向不同类型数据的指针之间进行转换可能是个问题,但这通常没有问题。类型 void * 被明确定义为通用数据指针:它是 malloc return 的类型,并且它被定义为可以安静地转换为任何对象指针类型——也就是说,你是甚至不需要演员表。所以对于dlsym的return类型几乎是一个很好的选择,除了对于函数指针的小问题。 malloc 从来没有这个问题(你几乎不会尝试 malloc 一个指向函数的指针),而 dlsym 总是有这个问题(你通常试图在动态加载的对象中访问的符号文件是代码至少和它们是数据一样频繁)。但是函数指针是 void * 不能保证转换成的,所以你很可能会收到警告,这就是你需要强制转换的原因,你可能会在 -pedantic 下收到警告,即使演员表。

dlsym() returns 一个 void* 值。此指针值可以引用对象或函数。

如果它指向一个对象,则不需要强制转换,因为 C 定义了从 void* 到任何指向对象的指针类型的隐式转换:

int *ptr = dlsym(handle, "name");

如果它指向一个函数(这可能更常见),则没有从 void* 到任何指向函数的指针类型的隐式转换,因此需要强制转换。

在标准 C 中,无法保证 void* 值可以有意义地转换为函数指针。 POSIX,它定义了 dlsym(),隐式保证 dlsym() 返回的 void* 值可以 有意义地转换为指针-函数类型——只要目标是相应函数的正确类型。

假设我们正在处理一个没有参数的 void 函数:

typedef void (*func_ptr_t)(void);
func_ptr_t fptr = (func_ptr_t)dlsym(handle, "name");

碰巧,gcc(带有 -pedantic)对此发出警告:

warning: ISO C forbids conversion of object pointer to function pointer type

这个警告并不完全正确。 ISO C 实际上禁止 将对象指针转换为函数指针类型。该标准列出了几个强制转换运算符不得违反的约束;将 void* 转换为函数指针类型不是其中之一。 C 标准没有定义这种转换的 行为 ,但是由于 POSIX 在这种特殊情况下定义了这不是问题。

对 Steve Summit 的回答的评论表明:

*(void **) (&f) = dlsym(handle, "foo");

将使 gcc 的警告静音。它会,但是它做出了 C 或 POSIX 不能保证的假设。 POSIX保证dlsym的结果可能转换为函数指针;它不保证它具有相同的表示或对齐方式。它可能会起作用,但我不推荐它。