由于该函数是存储在一个连续内存块中的指令集.
并且函数(入口点)的地址是函数中第一条指令的地址.(据我所知)
因此我们可以说函数的地址和函数中第一条指令的地址是相同的(在这种情况下,第一条指令是变量的初始化.).
但是下面的程序与上述内容相矛盾.
码:
#include#include #include using namespace std; char ** fun() { static char * z = (char*)"Merry Christmas :)"; return &z; } int main() { char ** ptr = NULL; char ** (*fun_ptr)(); //declaration of pointer to the function fun_ptr = &fun; ptr = fun(); printf("\n %s \n Address of function = [%p]", *ptr, fun_ptr); printf("\n Address of first variable created in fun() = [%p]", (void*)ptr); cout< 一个输出示例是:
Merry Christmas :) Address of function = [0x400816] Address of first variable created in fun() = [0x600e10]所以,这里函数的地址和函数中第一个变量的地址不一样.为什么这样?
我在谷歌搜索但无法提出确切的要求答案,并且对这种语言不熟悉我完全无法在网上捕捉到一些内容.
1> Sourav Ghosh..:所以,这里函数的地址和函数中第一个变量的地址不一样.为什么这样?
为什么会这样?函数指针是指向函数的指针.无论如何,它并没有指向函数内部的第一个变量.
详细说明,函数(或子例程)是指令集合(包括变量定义和不同的语句/操作),它们根据需要执行特定作业,大多数时间.它不仅仅是指向函数内部元素的指针.
函数内定义的变量不存储在与可执行机器代码相同的存储区中.基于存储类型,函数内存在的变量位于执行程序的存储器的某些其他部分中.
当构建程序(编译成目标文件)时,程序的不同部分以不同的方式组织.
通常,函数(可执行代码)驻留在称为代码段的单独段中,通常是只读存储器位置.
在编译时分配的变量,OTOH,被存储到数据段.
函数局部变量通常在需要时填充到堆栈内存中.
因此,没有这样的关系,函数指针将产生函数中存在的第一个变量的地址,如源代码所示.
在这方面,引用维基文章,
函数指针指向内存中的可执行代码,而不是引用数据值.
因此,TL; DR,函数的地址是可执行指令所在的代码(文本)段内的存储器位置.
2> Aganju..:函数的地址只是传递此函数的一种符号方式,就像在调用中传递它一样.可能,您获得的函数地址的值甚至不是指向内存的指针.
函数的地址适用于两件事:
比较平等
p==q
,和取消引用和打电话
(*p)()
您尝试做的任何其他事情都是未定义的,可能会也可能不会,并且是编译器的决定.
特别是,您可以轻松地拥有一个符合C++的实现,该实现被设计为以面向对象语言(如Java)编写的解释器.此实现中的函数指针可能类似于不透明字节码对象数组中的整数索引,并且根本没有与机器代码(甚至解释器代码)的连接.
3> Cort Ammon -..:好吧,这会很有趣.我们从C++中的函数指针一直到汇编代码级别的极其抽象的概念,并且由于我们遇到的一些特殊混淆,我们甚至可以讨论堆栈!
让我们从高度抽象的方面开始,因为这显然是你从一开始就要做的事情的一面.你有一个
char** fun()
正在玩的功能.现在,在这个抽象级别,我们可以看一下函数指针允许的操作:
我们可以测试两个函数指针是否相等.如果两个函数指针指向相同的函数,则它们是相等的.
我们可以对这些指针进行不等式测试,允许我们对这些指针进行排序.
我们可以使用一个函数指针,这会产生一个"函数"类型,这个类型实际上很容易混淆,我现在会选择忽略它.
我们可以使用您使用的符号"调用"函数指针:
fun_ptr()
.这意味着调用正在指向的函数.
这就是他们在抽象层面所做的一切.在其下方,编译器可以自由地实现它,但他们认为合适.如果一个编译器想要一个
FunctionPtrType
实际上是程序中每个函数的某个大表的索引,他们就可以.但是,这通常不是如何实现的.在将C++编译为汇编/机器代码时,我们倾向于利用尽可能多的特定于体系结构的技巧来节省运行时.在现实生活中的计算机上,几乎总是有一个"间接跳转"操作,它读取一个变量(通常是一个寄存器),然后跳转开始执行存储在该存储器地址的代码.它几乎是一般的,函数被编译成连续的指令块,所以如果你跳转到块中的第一条指令,它具有调用该函数的逻辑效果.第一条指令的地址碰巧满足了C++的函数指针抽象概念所要求的每一个比较,它恰好是硬件需要使用间接跳转来调用函数的值!这很方便,几乎每个编译器都选择以这种方式实现它!
但是,当我们开始讨论为什么你认为你所看到的指针与函数指针相同时,我们必须进入一些更细微的东西:段.
静态变量与代码分开存储.这有几个原因.一个是你希望你的代码尽可能紧.您不希望代码与内存空间一起存储变量.它效率低下.你必须跳过各种各样的东西,而不仅仅是犁过它.还有一个更现代的原因:大多数计算机允许您将某些内存标记为"可执行"和一些"可写".这样做有助于极大地对付一些真正邪恶的黑客技巧.我们试图永远不会同时标记可执行和可写的内容,以防黑客巧妙地找到一种方法来欺骗我们的程序用自己的方法覆盖我们的一些功能!
因此,通常存在一个
.code
段(使用该点符号仅仅因为它是在许多体系结构中记录它的流行方式).在此细分中,您可以找到所有代码.静态数据会在某个地方进行.bss
.因此,您可能会发现静态字符串存储在远离其上运行的代码的位置(通常距离至少4kb,因为大多数现代硬件允许您在页面级别设置执行或写入权限:在许多现代系统中页面为4kb )现在最后一块......堆栈.你提到以混乱的方式将东西存储在堆栈中,这表明快速浏览它可能会有所帮助.让我快速递归函数,因为它们更有效地展示了堆栈中发生的事情.
int fib(int x) { if (x == 0) return 0; if (x == 1) return 1; return fib(x-1)+fib(x-2); }该函数使用相当低效但清晰的方法计算Fibonacci序列.
我们有一个功能,
fib
.这意味着&fib
总是指向同一个地方的指针,但我们很明显地调用了很多次,所以每个人都需要自己的空间吗?在堆栈上我们有所谓的"帧".帧不是函数本身,而是它们是允许函数的这种特定调用使用的内存部分.每次你调用一个函数时
fib
,你都会在堆栈上为它的帧分配更多的空间(或者更迂腐地,它会在你调用之后分配它).在我们的例子中,
fib(x)
显然需要存储fib(x-1)
执行时的结果fib(x-2)
.它不能将它存储在函数本身,甚至是.bss
段中,因为我们不知道它将被递归多少次.相反,它在堆栈上分配空间来存储自己的结果副本,fib(x-1)
同时fib(x-2)
在自己的帧中运行(使用完全相同的函数和相同的函数地址).当fib(x-2)
返回时,fib(x)
只需加载了旧的价值,这是某些尚未被别人感动,增加了结果,并返回它!它是如何做到的?实际上,每个处理器都支持硬件堆栈.在x86上,这称为ESP寄存器(扩展堆栈指针).程序通常同意将其视为指向堆栈中下一个可以开始存储数据的位置的指针.欢迎你移动这个指针来为框架建立自己的空间,并进入.当你完成执行时,你应该把所有东西都移回去.
实际上,在大多数平台上,函数中的第一条指令不是最终编译版本中的第一条指令.编译器会为您注入一些额外的操作来管理这个堆栈指针,这样您甚至不必担心它.在某些平台上,例如x86_64,这种行为通常是强制性的,并在ABI中指定!
所以我们有:
.code
segment - 存储函数指令的位置.函数指针将指向此处的第一条指令.此段通常标记为"执行/只读",以防止程序在加载后写入程序.
.bss
segment - 静态数据将被存储的位置,因为.code
如果它想成为数据,它不能成为"仅执行" 段的一部分.堆栈 - 您的函数可以存储框架,它可以跟踪那个瞬间所需的数据,仅此而已.(大多数平台上也可以使用它来存储在哪里返回信息给一个函数结束后)
堆 - 这没有出现在这个答案中,因为你的问题不包括任何堆活动.但是,为了完整起见,我把它留在了这里,这样以后就不会让你大吃一惊.
4> axiac..:在您的问题文本中,您说:
因此我们可以说函数的地址和函数中第一条指令的地址是相同的(在这种情况下,第一条指令是变量的初始化.).
但是在代码中,您没有获得函数中第一条指令的地址,而是获取函数中声明的某个局部变量的地址.
函数是代码,变量是数据.它们存储在不同的存储区域中; 它们甚至不在同一个内存块中.由于现在操作系统施加的安全限制,代码存储在标记为只读的内存块中.
据我所知,C语言没有提供任何方法来获取内存中语句的地址.即使它提供这样的机制,函数的开始(函数在内存中的地址)也不同于从第一个C语句生成的机器代码的地址.
在从第一个C语句生成代码之前,编译器生成一个函数prolog(至少)保存堆栈指针的当前值并为函数的局部变量腾出空间.这意味着在从C函数的第一个语句生成任何代码之前的几个汇编指令.
5> MikeCAT..:如你所说,函数的地址可能是(它将取决于系统)函数的第一条指令的地址.
这是答案.在典型环境中,指令不会与变量共享地址,其中相同的地址空间用于指令和数据.
如果它们共享同一地址,则通过分配变量来销毁指令!
6> haccks..:C++程序中函数的地址究竟是什么?
与其他变量一样,函数的地址是为其分配的空间.换句话说,它是存储由函数执行的操作的指令(机器代码)的存储器位置.
要理解这一点,请深入了解程序的内存布局.
程序的变量和可执行代码/指令存储在不同的存储器段(RAM)中.变量转到STACK,HEAP,DATA和BSS段中的任何一个,而可执行代码转到CODE段.查看程序的一般内存布局
现在您可以看到变量和指令有不同的内存段.它们存储在不同的存储位置.函数地址是位于CODE段的地址.
因此,您将第一个语句与第一个可执行指令混淆.调用函数调用时,程序计数器将使用函数的地址进行更新.因此,函数指针指向存储在存储器中的函数的第一条指令.