5

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

void foo() {}
void bar() {}

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

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")
}

我可以这样使用这些函数指针吗?我写了一个程序来测试它,看起来还可以。此外,我认为,与可能位于stackor中的普通对象不同heap,函数位于text segment(对吗?),所以当我提到 时foo,它是否给了我foo文本段中函数所在的物理地址?

跟进

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

ReturnType (*fptr)(ArgType) = beautiful_func;

这里有两种情况,

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

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

4

5 回答 5

3

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

但是,当您说“比较”时,请检查您的真实想法:

  • 您是否有兴趣检测到您被赋予了不同的“东西”
  • 或者你有兴趣检测你被赋予了不同的功能吗?

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

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

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

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

这比数组复杂一些。对于数组,如果数组不同,您将获得不同的指针。这里,同一个函数可能有很多不同的地址。在C++ 中使用模板时可能会特别明显,但这又取决于链接器的工作表现如何。哦,很好的例子:DLL。使用基于相似代码的三个 DLL,他们几乎可以保证拥有静态链接到的所有内容的三个副本。

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

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

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

  • 如果它们相同:

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

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

编辑跟进:

除非您(或您使用的某些库)曾经卸载DLL,否则您的指向函数的指针是安全的。

一旦加载了 DLL,唯一能改变这个 DLL 所占用的地址含义的坏事就是卸载动态模块

如果您确定:

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

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

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

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

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

那么你的函数指针根本无法安全使用。我的意思是无论如何。不要储存它,因为它可能会立即蒸发。

于 2012-11-28T08:11:10.747 回答
2

它是否给了我函数 foo 在文本段中的物理地址?

除非您正在使用原始操作系统或任何其他特殊操作系统,否则不!

地址不是物理地址,而是虚拟地址

基本上,操作系统采用了一种机制,允许程序甚至大于物理内存。所以操作系统在幕后完成了处理映射的工作。

对不起,如果我让你感到困惑。您的理解是正确的(以您使用函数指针的方式使用函数指针是完全可以的),但地址不是物理地址(指的是实际寻址主存储器时使用的数字)。

于 2012-11-28T07:51:42.993 回答
2

是的,C 标准允许您将函数指针与运算符 == 和 != 进行比较,例如从 C11 6.5.9 开始:

两个指针比较相等当且仅当两者都是空指针,两者都是指向同一个对象(包括指向对象的指针和其开头的子对象)或函数的指针,

函数的确切位置取决于您的平台,它可能位于文本段中,也可能位于其他位置。在具有虚拟内存的操作系统上运行时,地址通常是虚拟地址,而不是物理内存地址。

于 2012-11-28T08:14:16.287 回答
1

考虑到指针比内存地址存储的事实,归结为一个数字,是的,你可以这样比较。至于其他问题,取自此处,文本段将被定义为“目标文件或内存中的程序部分之一,其中包含可执行指令。”,这意味着该指针应该包含某个位置的地址文本段。

于 2012-11-28T07:49:06.123 回答
0

是的,您可以使用这种方式。而你的理解是正确的。

于 2012-11-28T07:44:28.537 回答