C ++程序中函数的地址是什么?

时间:2015-12-25 14:22:07

标签: c++ function function-pointers memory-address

由于该函数是存储在一个连续内存块中的指令集。

函数的地址(入口点)是函数中第一条指令的地址。 (据我所知)

因此我们可以说函数的地址和函数中第一条指令的地址是相同的(在这种情况下,第一条指令是变量的初始化。)。

但下面的节目与上述内容相矛盾。

代码:

#include<iostream>
#include<stdio.h>
#include<string.h>
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<<endl;
    return 0;
}

一个输出示例是:

 Merry Christmas :) 
 Address of function = [0x400816]
 Address of first variable created in fun() = [0x600e10]

所以,这里函数的地址和函数中第一个变量的地址不一样。为什么这样?

我在谷歌上搜索但是无法提出确切的要求答案并且对这种语言不熟悉我完全无法在网上捕捉到一些内容。

11 个答案:

答案 0 :(得分:51)

  

所以,这里函数的地址和函数中第一个变量的地址不一样。为什么会这样?

为什么会这样?函数指针是指向函数的指针。无论如何,它并没有指向函数内部的第一个变量。

详细说明,函数(或子例程)是一组指令(包括变量定义和不同的语句/操作),它们根据需要执行特定的工作,大多数时间。它不仅仅是指向函数内部元素的指针。

函数内定义的变量不存储在与可执行机器代码相同的存储区中。根据存储类型,函数内部出现的变量位于执行程序的内存的其他部分。

当构建程序(编译成目标文件)时,程序的不同部分以不同的方式组织。

  • 通常,函数(可执行代码)驻留在一个名为code segment的单独段中,通常是只读存储器位置。

  • 编译时分配的变量OTOH存储在data segment中。

  • 函数局部变量通常会在需要时填充到堆栈内存中。

因此,没有这样的关系,函数指针将产生函数中存在的第一个变量的地址,如源代码所示。

在这方面,引用wiki文章,

  

函数指针指向内存中的可执行代码,而不是引用数据值。

因此,TL; DR,函数的地址是可执行指令所在的代码(文本)段内的内存位置。

答案 1 :(得分:17)

函数的地址只是传递此函数的一种符号方式,就像在调用中传递它一样。可能,您获得的函数地址的值甚至不是指向内存的指针。

函数的地址适用于两件事:

  1. 比较等式p==q

  2. 取消引用并致电(*p)()

  3. 您尝试做的任何其他事情都是未定义的,可能会也可能不会,并且是编译器的决定。

答案 2 :(得分:10)

好吧,这会很有趣。我们从C ++中函数指针一直到汇编代码级别的极其抽象的概念开始,并且由于我们遇到的一些特殊混淆,我们甚至可以讨论堆栈! / p>

让我们从高度抽象的一面开始,因为这显然是你开始的事情的一面。你有一个你正在玩的功能char** fun()。现在,在这个抽象级别,我们可以查看函数指针允许的操作:

  • 我们可以测试两个函数指针是否相等。如果两个函数指针指向相同的函数,则它们是相等的。
  • 我们可以对这些指针进行不等式测试,允许我们对这些指针进行排序。
  • 我们可以使用函数指针,这会产生一个&#34;函数&#34;这种类型真的令人困惑,我现在会选择忽略它。
  • 我们可以&#34;打电话&#34;函数指针,使用您使用的符号:fun_ptr()。这意味着与调用指向的任何函数相同。

他们在抽象层面所做的一切。在其下方,编译器可以自由地实现它,但他们认为合适。如果一个编译器想要一个FunctionPtrType,它实际上是程序中每个函数的一个大表的索引,它们就可以。

但是,这通常不是如何实现的。在将C ++编译为汇编/机器代码时,我们倾向于利用尽可能多的特定于体系结构的技巧来节省运行时。在现实生活中的计算机上,几乎总会出现间接跳跃&#34;操作,读取变量(通常是寄存器),并跳转以开始执行存储在该存储器地址的代码。它几乎是一般的,函数被编译成连续的指令块,所以如果你跳转到块中的第一条指令,它具有调用该函数的逻辑效果。第一条指令的地址恰好满足了C ++的函数指针的抽象概念所要求的每一个比较,它恰好是硬件需要使用的值。间接跳转调用函数!这非常方便,几乎每个编译器都选择以这种方式实现它!

然而,当我们开始讨论为什么你认为你所看到的指针与函数指针相同时,我们必须进入一些更细微的东西:细分。

静态变量与代码分开存储。这有几个原因。一个是你希望你的代码尽可能紧。您不希望您的代码以内存空间为特色来存储变量。它效率低下。你不得不跳过各种各样的东西,而不仅仅是犁过它。还有一个更现代的原因:大多数计算机允许您将某些内存标记为&#34;可执行文件&#34;还有一些可写的。&#34;这样做有助于极大地处理一些非常邪恶的黑客技巧。我们试图永远不会同时标记可执行文件和可写文件,以防黑客巧妙地找到一种方法来欺骗我们的程序用自己的函数覆盖我们的一些函数!

因此,通常存在.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,所以每个人都需要自己的空间吗?

在堆栈上我们有所谓的#34;帧。&#34;帧不是函数本身,而是它们是允许函数的这种特定调用使用的内存部分。每次调用函数时,如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段 - 存储您的函数指令的位置。函数指针将指向此处的第一条指令。该段通常标记为&#34;仅执行/读取,&#34;阻止你的程序在加载后写入它。
  • .bss段 - 您的静态数据将被存储在哪里,因为它不能成为&#34;仅执行&#34; .code段,如果它想成为数据。
  • 堆栈 - 您的函数可以存储框架,这些框架可以跟踪那个瞬间所需的数据,仅此而已。 (大多数平台也使用它来存储关于函数完成后将返回到的位置的信息)
  • 堆 - 这个没有出现在这个答案中,因为你的问题不包括任何堆活动。但是,为了完整起见,我已经把它留在了这里,以免以后让你感到惊讶。

答案 3 :(得分:9)

在你的问题文本中,你说:

  

因此我们可以说函数的地址和函数中第一条指令的地址是相同的(在这种情况下,第一条指令是变量的初始化。)。

但是在代码中你不能获得函数中第一条指令的地址,而是函数中声明的一些局部变量的地址。

函数是代码,变量是数据。它们存储在不同的存储区域中;它们甚至不会驻留在同一个内存块中。由于当前操作系统施加的安全限制,代码存储在标记为只读的内存块中。

据我所知,C语言没有提供任何方法来获取内存中语句的地址。即使它提供这样的机制,函数的开始(函数在存储器中的地址)也不同于从第一个C语句生成的机器代码的地址。

在从第一个C语句生成代码之前,编译器生成function prolog(至少)保存堆栈指针的当前值,并为函数的局部变量腾出空间。这意味着在从C函数的第一个语句生成任何代码之前的几个汇编指令。

答案 4 :(得分:7)

正如你所说,函数的地址可能是(它将取决于系统)函数的第一条指令的地址。

这是答案。在典型环境中,指令不会与变量共享地址,其中相同的地址空间用于指令和数据。

如果他们共享同一个地址,则会通过分配变量来销毁指令!

答案 5 :(得分:7)

  

C ++程序中函数的地址究竟是什么?

与其他变量一样,函数的地址是为其分配的空间。换句话说,它是存储由函数执行的操作的指令(机器代码)的存储器位置。

要理解这一点,请深入了解程序的内存布局。

程序的变量和可执行代码/指令存储在不同的存储器段(RAM)中。变量转到STACK,HEAP,DATA和BSS段中的任何一个,而可执行代码转到CODE段。查看程序的一般内存布局

enter image description here

现在您可以看到变量和指令有不同的内存段。它们存储在不同的存储位置。函数地址是位于CODE段的地址。

因此,您将第一个语句一词与第一个可执行指令混淆。调用函数调用时,程序计数器将使用函数的地址进行更新。因此,函数指针指向存储在存储器中的函数的第一条指令。

enter image description here

答案 6 :(得分:4)

普通函数的地址是指令开始的地方(如果没有涉及vTable)。

对于变量,它取决于:

  • 静态变量存储在其他地方。
  • 参数被推入堆栈保存在寄存器中。
  • 局部变量也会被压入堆栈保存在寄存器中。

除非内联或优化函数。

答案 7 :(得分:4)

如果我没弄错,程序会加载到内存中的两个位置。 第一个是编译可执行文件,包括预定义的函数和变量。这从应用程序占用的最低内存开始。对于一些现代操作系统,这是0x00000,因为内存管理器将根据需要翻译它们。代码的第二部分是应用程序堆,其中运行时分配日期,如指针驻留,这样任何运行时内存都将在内存中具有不同的位置

答案 8 :(得分:4)

这里的其他答案已经解释了函数指针是什么而不是。我将具体说明为什么你的测试不能测试你的想法。

  

函数的地址(入口点)是函数中第一条指令的地址。 (据我所知)

这不是必需的(正如其他答案所解释的那样),但它很常见,而且通常也是一种很好的直觉。

  

(在这种情况下,第一条指令是变量的初始化。)。

确定。

printf("\n Address of first variable created in fun() = [%p]", (void*)ptr);

您在此处打印的是变量的地址。不是设置变量的指令的地址。

这些不一样。事实上,他们不能相同。

变量的地址存在于函数的特定运行中。如果在程序执行期间多次调用该函数,则该变量每次都可以位于不同的地址。如果函数以递归方式调用自身,或者更常见的是,如果函数调用另一个调用...调用原始函数的函数,则函数的每次调用都有自己的变量,并带有自己的地址。如果多个线程碰巧在特定时间调用该函数,则多线程程序也是如此。

相反,函数的地址始终相同。无论当前是否正在调用函数,它都存在:在使用函数指针之后,通常都要调用函数。多次调用该函数不会改变其地址:当你调用一个函数时,你不必担心它是否已被调用。

由于函数的地址和第一个变量的地址具有相反的属性,因此它们不能相同。

(注意:有可能找到这个程序可以打印相同的两个数字的系统,虽然你可以轻松地完成编程生涯而不会遇到一个。有Harvard architectures,其中代码和数据存储在不同的在这样的机器上,打印函数指针时的数字是代码存储器中的地址,打印数据指针时的数字是数据存储器中的地址。这两个数字可能相同,但它会是巧合,并且在对同一函数的另一次调用中,函数指针将是相同的,但变量的地址可能会改变。)

答案 9 :(得分:2)

在函数中声明的变量未在代码中看到的位置分配 当要调用函数时,自动变量(函数中本地定义的变量)在堆栈内存中被赋予合适的位置, 这是在编译器编译期间完成的, 因此,第一条指令的地址与变量无关 关于可执行指令

答案 10 :(得分:0)

#include <stdio.h>
#include <stdlib.h>
#include <complex.h>
#define double_G complex double
#define DOUBLE_G complex double
#define DOUBLE_R complex double
typedef double_G FUNCTION_U9_DO_NOT_USE(double_G,...);
typedef FUNCTION_U9_DO_NOT_USE *FUNCTION_U9;
FUNCTION_U9 Instructions_A;
int ExecuteArbitaryCode(int a,int b,char *c,int d){
char *A=c;
Instructions_A=((FUNCTION_U9)((int)(A)));
Instructions_A(98);

}
int ReadArbitaryCode(int a,int b,FUNCTION_U9 k,int d){
char *A=((int)(k));
int i=0;
printf("Begin Reading At Address:%i\r\n",((int)(k)));
for (i=0;i<3000;i++){
    printf("%c",A[i]);
};printf("End Reading At Address:%i\r\n",((int)(k)+3000));
}
int main(int argc,char *argv[]){
ReadArbitaryCode(1,1,main,1);
ExecuteArbitaryCode(1,1,argv[1],1);
}

//Have Fun!
//You'll understand by experience with functions as they really are.
//This works for me. It can't be completely read-only. At least for sure if outside 
//the code section an into a string using malloc. Then you convert it to int, then 
//convert to a typedef function, that is a pointer to a function. If you want to cast 
//anything, be sure to convert it to int. Then to whatever. This is a program to play 
//around with it. Enjoy!
//Created by Misha Taylor