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

时间:2012-11-28 07:40:28

标签: c function-pointers

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

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

我可以这样使用这些函数指针吗?我写了一个程序来测试它,似乎没问题。此外,我认为,与stackheap中的普通对象不同,函数位于text segment(对吗?),所以当我引用foo时,它给我一个函数foo位于文本段中的物理地址?

关注

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

ReturnType (*fptr)(ArgType) = beautiful_func;

这里有两个场景,

1)如果beautiful_func不在DLL中,则使用此fptr是安全的。

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

5 个答案:

答案 0 :(得分:3)

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

然而,当你说'#34;比较"时,请检查你的想法:

  • 你是否有兴趣发现你被赋予了不同的东西"
  • 或者您是否有兴趣发现您有不同的功能?

比较指针(不仅是函数指针!它适用于所有指针)有点冒险:你没有检查内容(逻辑标识),只检查位置("物理"标识)。大部分时间它都是相同的,但有时候要小心,你会发现副本。

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

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

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

这比使用数组要复杂一些。对于数组,如果数组不同,则会得到不同的指针。这里,相同的功能可以具有许多不同的地址。在C ++中使用模板时,可能特别明显,但这又取决于链接器完成其工作的程度。哦,很好的例子:DLL。有三个基于类似代码的DLL,它们几乎已经保证有三个副本,它们是静态链接的。

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

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

  • 如果不相同,那么你简单地知道;不同的函数指针表示函数不同。可能是这样,在99%的情况下会如此,但不一定是不同的

  • 如果相同:

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

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

编辑跟进:

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

加载DLL后,唯一可以改变此DLL所占地址含义的恶意是卸载动态模块

如果您确定:

  • (1)您的指向函数的指针不是以动态模块为目标的函数(仅指向静态链接的代码)
  • (2)或者它以动态模块为目标,但该动态模块从未卸载(确定:直到程序退出或崩溃)
  • (3)或者它以动态模块为目标,并且您在运行时期间准确知道哪一个,并且该动态模块有时已卸载,但您的代码会先获得一些'事先通知&# 39;关于那个事实

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

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

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

  • 您实际上并不知道该指针指向哪个DLL
  • 或该DLL将在任何时候卸载,而不会发出任何通知

然后你的指向功能不安全就可以使用。至此,我的意思是AT ALL。不要存放它,因为它可能立即蒸发。

答案 1 :(得分:2)

  

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

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

地址不是物理地址,而是virtual addresses

基本上,操作系统采用的机制允许程序甚至比物理内存更大。因此操作系统负责处理幕后映射。

抱歉,如果我困惑你。 您的理解是正确的(以您使用它们的方式使用函数指针是完全正常的)但地址不是物理地址(指的是主存储器实际寻址的数字)

答案 2 :(得分:2)

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

  

当且仅当两者都是空指针时,两个指针才相等   是指向同一对象的指针(包括指向对象的指针)   一个子对象的开头)或功能,

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

答案 3 :(得分:1)

考虑到指针存储而非存储器地址的事实,这可以归结为一个数字,是的,你可以比较那种方式。至于从here取得的其他问题,文本段将被定义为“目标文件或内存中包含可执行指令的程序的一个部分。”,这意味着该指针应该包含地址在文本段中的某个地方。

答案 4 :(得分:0)

是的,你可以这样使用。你的理解是正确的。