调用函数时,执行转移到函数指针指示的点。在执行开始时,必须从磁盘加载可执行代码。
如何调用正确的函数指针?可执行代码每次都不会映射到同一位置的虚拟内存中,对吧?那么运行时如何确保对函数的调用总是调用正确的函数,即使每次执行的可执行代码的位置不同?
请考虑以下代码:
void func(void); //Func defined in another dynamic library
int main()
{
func();
//How is the pointer to func known if the file containing func is loaded from disk at run time?
};
答案 0 :(得分:2)
解析函数指针的方式非常简单。当编译器链发出可执行二进制文件时,所有内部地址都相对于“基地址”。在某些可执行格式中,指定了这个基址,而在其他格式中则暗示了它。
基本上,编译器假定它假定执行将从地址A开始。运行时决定它实际上应该从B开始。运行时然后减去A并在执行之前将B添加到二进制中的所有非相对地址。
此过程也适用于DLL等。动态库存储相对于指向每个导出函数的基指针的地址列表。名称通常也与列表相关联,因此您可以按名称引用函数。加载库时,地址转换将应用于所有内容,包括地址表。此时,调用者只需查找已翻译的表中的地址,然后它们将具有给定函数的绝对地址。
在较旧的操作系统中,很久以前(有时甚至是今天),在地址空间布局随机化,内存页面和多任务操作系统之类的东西之前,程序只会复制到指定的基址中。然后执行的内存。
在现代操作系统中,根据平台和应用程序的功能或要求,可能会发生以下几种情况之一。大多数操作系统都处理我在第二段中描述的本机二进制文件,但是某些应用程序(例如在以后的体系结构上运行16位x86)可能涉及更复杂的策略。一种这样的策略涉及为代码提供静态虚拟地址空间。这有各种限制,例如,如果您希望它与外部代码(如窗口控制台或网络堆栈)交互,则需要仿真/兼容性层。
尽管对16位支持的需求有所下降,但这种方案的使用越来越少。为所有程序提供自己独特的地址空间(而不是让它重叠)可以促进共享库,服务和其他共享内容的使用。
答案 1 :(得分:0)
通常,函数调用是静态解析的。编译文件时,首先创建.o(或.obj)文件。所有已知地址 - 都是本地函数(来自此文件)。未知是“外部”功能。 然后,执行链接。链接完成每个“extern”函数的地址映射。如果缺少任何名称 - 发生链接错误。
如何调用正确的函数指针? 函数指针是函数地址,函数名是函数地址。两者都是值,而不是L值。 & func和func完全相同。
加载或PE(或ELF)文件是一个进程或将可执行文件加载到内存。要解释的信息太多了。基本上,只是为了澄清,请考虑:每个函数在进程地址空间中都有自己的地址。
答案 2 :(得分:-1)
您可以打印' func'并查看在每次执行期间是否具有相同的地址,如下所示:
printf("%u", function);
对我而言,每次都是相同的地址(虚拟内存)。