1

host.cpp 有:

int main (void)
{
    void * th = dlopen("./p1.so", RTLD_LAZY);
    void * fu = dlsym(th, "fu");

    ((void(*)(int, const char*)) fu)(2, "rofl");

    return 0;
}

p1.cpp 有:

#include <iostream>

extern "C" bool fu (float * lol)
{
    std::cout << "fuuuuuuuu!!!\n";
    return true;
}

(我故意留下错误检查)

执行主机时,“fuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu” 打印正确,即使我使用完全不同的函数签名将 void 指针类型转换为符号。

为什么会发生这种情况?不同编译器之间的这种行为是否一致?

4

4 回答 4

5

发生这种情况是因为 UB,并且这种行为与任何事情都不一致,根本,永远,出于任何原因。

于 2012-09-24T20:43:27.040 回答
4

因为 void 指针中没有关于函数签名的信息。或地址以外的任何信息。如果您开始使用参数,您可能会遇到麻烦。

于 2012-09-24T20:42:01.040 回答
2

这实际上不是创建将失败的案例的一个很好的例子,因为:

  1. 您永远不会使用函数中的参数fu
  2. 您的函数fu的参数(或激活框架本身的内存占用量较小)比您要转换的函数指针类型少,因此您永远不会遇到fu尝试访问外部内存的情况调用者设置的激活记录。

最后,你所做的仍然是未定义的行为,但你没有做任何事情来创建可能导致问题的违规行为,因此它最终成为一个静默错误。

这种行为在不同的编译器之间是否一致?

不。如果您的平台/编译器使用了要求被调用者清理堆栈的调用约定,那么糟糕,如果被调用者和调用者之间的激活记录大小不匹配,那么您很可能会被淹没期望......在被调用者返回时,堆栈指针将被移动到错误的位置,可能会破坏堆栈,并完全弄乱任何堆栈指针的相对寻址。

于 2012-09-24T20:43:36.017 回答
1

刚刚发生了,那

  • C使用cdecl调用转换(所以调用者清除堆栈)
  • 您的函数不使用给定的参数参数

所以你的电话似乎工作正常。

但实际上行为是未定义的。更改签名或使用参数将导致您的程序崩溃:

添加:

例如,考虑stdcall调用转换,其中被调用者清除堆栈。在这种情况下,即使你为调用者和被调用者都声明了正确的调用转换,你的程序仍然会崩溃,因为你的堆栈将被破坏,因为被调用者会根据它的签名清除它,但调用者会根据另一个签名填充:

#include <iostream>
#include <string>

extern "C" __attribute__((stdcall)) __attribute__((noinline)) bool fu (float * lol) 
{
    std::cout << "fuuuuuuuu!!!\n";
    return true;
}

void x()
{
    (( __attribute__((stdcall)) void(*)(int, const char*)) fu)(2, "rofl");
}

int main (void)
{
    void * th = reinterpret_cast<void*>(&fu);

    std::string s = "hello";

    x();

    std::cout << s;

    return 0;
}
于 2012-09-24T20:45:42.430 回答