传递函数指针并像普通对象的指针一样比较它们是否安全?

Alc*_*ott 5 c function-pointers

这是事情,我有几个功能,

void foo() {}
void bar() {}
Run Code Online (Sandbox Code Playgroud)

我想像普通对象的指针一样传递这些函数,

int main()
{
    void (*fptr1)() = foo;
    void (*fptr2)() = fptr1;
    void (*fptr3)() = bar;

    if (fptr1 == foo)
        printf("foo function\n");
    if (fptr2 == foo)
        printf("foo function\n");
    if (fptr3 == foo)
        printf("foo function\n")
}
Run Code Online (Sandbox Code Playgroud)

我可以这样使用这些函数指针吗?我写了一个测试它的程序,好像没问题.此外,我认为,不像普通对象可能位于stack或者heap,函数驻留在text segment(对吗?)中,所以当我提到foo它时,它是否给了我函数foo在文本段中的物理地址?

跟进

如果我确实使用DLL,请考虑这个:首先,为函数ptr fptr分配一个函数,

ReturnType (*fptr)(ArgType) = beautiful_func;
Run Code Online (Sandbox Code Playgroud)

这里有两个场景,

1)如果beautiful_func不在DLL中,那么使用它是安全的fptr.

2)如果它在DLL中,那么以后,我认为使用它是不安全的fptr,因为它现在可能指的是一个完全不同的功能,它不是fptr天生的,对吧?

que*_*atl 3

您可以通过简单地 == 来检查两个函数指针是否相等,因为它们只是普通指针。这是显而易见的。

然而,当你说“比较”时,检查一下你的真实想法:

  • 你有兴趣发现你被给予了不同的“东西”吗
  • 或者您有兴趣发现您被赋予了不同的功能吗?

比较指针(不仅是函数指针!它适用于所有指针)有点冒险:您没有检查内容(逻辑标识),而只是检查位置(“物理”标识)。大多数时候它确实是相同的,但有时要小心,你会偶然发现副本。

很明显,如果您创建一个包含数字 1,2,3,4 的数组,然后分配另一个数组并将内容复制到那里,那么您会得到两个不同的指针,对吧?但该阵列对您来说可能是相同的,具体取决于您需要它的用途。

对于函数指针,问题是相同的,甚至更多:您实际上不知道编译器/链接器对您的代码做了什么。它可能优化了一些东西,如果它发现它们相等,它可能会将一些未导出的函数合并在一起,它可能复制或内联其他函数。

尤其是在处理更大的独立“子项目”时,可能会发生这种情况。想象一下,您编写了一个排序函数,然后将其包含在子项目 A 和子项目 B 中,编译/构建所有内容,然后链接并运行。你会以一个或两个排序函数结束吗?在您实际检查并正确调整链接选项之前,这个问题很难回答。

这比数组稍微复杂一些。对于数组,如果数组不同,您会得到不同的指针。这里,同一个函数可能有许多不同的地址。在使用 C++ 中的模板时,这一点可能尤其引人注目,但这又取决于链接器的工作表现。哦,很好的例子:DLL。通过使用基于相似代码的三个 DLL,它们几乎可以保证拥有静态链接到的所有内容的三个副本。

当谈论 DLL 时...您知道它们可以将附加代码加载/卸载到您的内存中,对吧?这意味着当您加载 DLL 时,在某个地址 XYZ 处会出现一个函数。然后你卸载它,它就消失了。但是当你现在加载不同的 DLL 时呢?当然,操作系统可以重用该空间,并且可以将新加载的 DLL 映射到与前一个 DLL 相同的区域。大多数时候你不会注意到它,因为新加载的 DLL 将被映射到不同的区域,但它可能会发生

这意味着虽然您可以比较指针,但您得到的唯一答案是:指针是否相同?

  • 如果它们不一样,那么你根本不知道;不同的函数指针并不意味着函数不同。可能是这样,99%的情况都是如此,但不一定不同

  • 如果它们相同:

    • 如果您没有多次加载/卸载各种动态库,您可能会认为没有任何变化,并且您可以确定获得了与以前相同的函数/对象/数组

    • 如果您正在使用可卸载的动态模块,您最好根本不要这样假设,除非您完全确定没有任何指针来自将来将被卸载的 DLL。请注意,某些库使用动态库来实现“类似插件”的功能。请小心它们的指针,并注意插件加载/卸载通知。卸载动态库时,您的函数可能会发生变化。

编辑后续内容:

除非您(或您使用的某些库)卸载了DLL,否则您的指向 DLL 目标函数的指针可以安全使用。

一旦加载了 DLL,唯一可以改变该 DLL 所采用的地址含义的邪恶行为就是卸载动态模块

如果您确定:

  • (1) 或者您的函数指针不针对动态模块中的函数(仅指向静态链接代码)
  • (2) 或者它以动态模块为目标,但该动态模块永远不会被卸载(好的:直到程序退出或崩溃)
  • (3) 或者它以动态模块为目标,并且您确切地知道是哪个动态模块,并且该动态模块有时会在运行时卸载,但您的代码会收到有关该事实的一些“事先通知”

那么只要您添加一些安全措施,您的函数指针就可以安全地存储、使用和比较:

  • 对于(1),不需要安全措施:功能不会被替换
  • 对于(2),不需要安全措施:直到程序退出才能获得这些功能
  • 对于(3),需要采取安全措施:您必须监听这些通知,并且一旦收到DLL被卸载的通知,您必须立即忘记所有指向DLL的指针。您仍然可以安全地记住任何其他内容。当它再次加载时,您仍然可以安全地重新记住它。

如果您怀疑函数指针确实针对动态模块中的函数,该函数将在程序退出之前的某个时间点卸载,并且:

  • 你实际上并不知道该指针指向哪个 DLL
  • 或者该 DLL 将在任何时候被卸载,恕不另行通知

那么你的函数指针根本无法安全使用。我所说的“完全”是指以任何方式“完全”。不要储存它,因为它可能会立即蒸发。