【问题标题】:Do functions occupy memory space?函数是否占用内存空间?
【发布时间】:2014-02-12 17:38:32
【问题描述】:
void demo()
{
    printf("demo");
}

int main()
{
    printf("%p",(void*)demo);
    return 0;
}

上面的代码打印出函数demo的地址。
所以如果我们可以打印出一个函数的地址,那意味着这个函数存在于内存中并且占用了其中的一些空间。
那么它在内存中占用了多少空间呢?

【问题讨论】:

标签: c function memory


【解决方案1】:

您可以使用objdump -r -d 亲自查看:

0000000000000000 <demo>:
   0:   55                      push   %rbp
   1:   48 89 e5                mov    %rsp,%rbp
   4:   bf 00 00 00 00          mov    $0x0,%edi
                        5: R_X86_64_32  .rodata
   9:   b8 00 00 00 00          mov    $0x0,%eax
   e:   e8 00 00 00 00          callq  13 <demo+0x13>
                        f: R_X86_64_PC32        printf-0x4
  13:   5d                      pop    %rbp
  14:   c3                      retq   

0000000000000015 <main>:

编辑

我获取了您的代码并编译(但未链接!)它。使用objdump,您可以看到编译器布置要运行的代码的实际方式。归根结底,没有函数这样的东西:对于 CPU 来说,它只是跳转到某个位置(在这个清单中恰好被标记)。所以“函数”的大小就是包含它的代码的大小。


似乎有些混淆,这不是“真正的代码”。 GDB 是这样说的:

Dump of assembler code for function demo:
   0x000000000040052d <+0>:     push   %rbp
   0x000000000040052e <+1>:     mov    %rsp,%rbp
   0x0000000000400531 <+4>:     mov    $0x400614,%edi
   0x0000000000400536 <+9>:     mov    $0x0,%eax
   0x000000000040053b <+14>:    callq  0x400410 <printf@plt>
   0x0000000000400540 <+19>:    pop    %rbp
   0x0000000000400541 <+20>:    retq   

这是完全相同的代码,大小完全相同,由链接器修补以使用真实地址。 gdb 以十进制打印偏移量,而objdump 使用更有利的十六进制。如您所见,在这两种情况下,大小都是 21 字节。

【讨论】:

  • 一些解释会有所帮助
  • @embert:这些是编译器为您的函数演示生成的机器指令。您在屏幕上看到的不是内存中的内容,而是那些指令的数字表示。
  • objdump的输出与程序在ram内存中的存储方式无关。
  • @JoséTomásTocino:不完全正确。这些是将要加载到 RAM 中的指令的文本表示形式。
  • @JoséTomásTocino:你是什么意思?右边的二进制是此架构的机器代码的二进制表示。当然,另一个拱门会有所不同,但在一个 objdump 上运行(猜测 x86 或 x64),它应该是相同的。
【解决方案2】:

所以如果我们可以打印一个函数的地址,那就意味着这个 函数存在于内存中并占用了一些空间。

是的,您编写的函数会被编译成存储在内存中的代码。(在解释型语言的情况下,代码本身保存在内存中并由解释器执行。) p>

那么它在内存中占用了多少空间呢?

内存量完全取决于函数。您可以编写一个很长的函数,也可以编写一个很短的函数。长的将需要更多的内存。但是,用于代码的空间通常不是您需要担心的,除非您在具有严重内存限制的环境中工作,例如在非常小的嵌入式系统上。在具有现代操作系统的台式计算机(甚至移动设备)上,虚拟内存系统将根据需要将代码页面移入或移出物理内存,因此您的代码也几乎不会消耗很多内存。

【讨论】:

  • @kevingomes:空函数很可能会被编译器优化掉。如果不是,它将由函数设置和拆卸操作组成。
  • @kevingomes:通过使用函数指针引用函数,编译器将无法优化函数,并且必须在最终代码中包含一个刚刚设置和拆除的函数。通过引用该函数,您可以强制编译器包含它。
  • “设置和拆除”具有误导性。一个好的编译器会将返回类型为 void 的空函数编译为单个 ret 指令(或类似指令)。
  • 函数的最小可能大小取决于架构,但通常是一两条机器指令。更古怪的情况包括 SPARC,它的返回指令上有一个延迟槽,以及 Itanic,它的伪 VLIW 特性意味着一个“包”中的机器指令不能少于三个,尽管其中两个可以是 NOP。此外,ABI 可能要求所有函数入口地址与指令大小的某个倍数对齐,这有效地增加了任何一个函数占用的最小空间。
  • (In the case of an interpreted language, the code itself is kept in memory and executed by an interpreter.) 严格来说这不是真的,大多数现代语言编译成一些的较低级别的字节码,而不是在内存中存储文本或 AST。
【解决方案3】:

当然是在内存中占空间,一旦你执行整个程序就会被加载到内存中。通常,程序指令存储在内存空间的最低字节中,称为text section。你可以在这里阅读更多信息:http://www.geeksforgeeks.org/memory-layout-of-c-program/

【讨论】:

  • 程序必须加载到某种形式的内存中,但不一定是 RAM。许多微控制器都有一个在闪存或 ROM 中实现的程序存储。
【解决方案4】:

是的,您在代码中使用的所有函数都会占用内存空间。但是,内存空间不一定专属于您的功能。例如,inline 函数将占用调用它的每个函数内部的空间。

该标准没有提供一种方法来判断函数在内存中占用了多少空间,因为指针算术,让您计算数据内存中连续内存区域大小的技巧,没有为函数指针定义。此外,ISO C 禁止将函数指针转换为对象指针类型,因此您无法通过将函数指针转换为 char* 来绕过此限制。

printf("%p",demo);

上面的代码打印出函数demo()的地址。

这是未定义的行为:%p 需要 void*,而您传递给它的是 void (*)()。您应该会看到一个编译器警告,告诉您您正在执行的操作无效 (demo)。

【讨论】:

    【解决方案5】:

    至于确定它占用的内存量,这在运行时是不可能的。但是,还有其他方法可以确定它: How to get the length of a function in bytes?

    【讨论】:

      【解决方案6】:

      这些函数被编译成机器代码,只能在特定的 ISA(x86,如果要在手机上运行,​​可能是 ARM 等)上运行。由于不同的处理器可能需要更多或更少的指令来运行相同的函数,而且指令的长度也可能不同,在编译之前无法提前知道函数的确切大小。

      即使您知道它将针对什么处理器和操作系统进行编译,不同的编译器也会根据它们使用的指令以及优化代码的方式创建不同的、等效的函数表示。

      另外,请记住,函数以不同的方式占用内存。我认为您在谈论代码本身,这是它自己的部分。在执行过程中,函数也可以占用栈空间——每次调用函数时,都会以栈帧的形式占用更多的内存。数量取决于函数声明的局部变量和参数的数量和类型。

      【讨论】:

      • 函数绝对存储在堆栈中。 -1.
      • @Linuxios 但前两个段落很好,GVH 应该删除最后一个段落
      • 我的错误 - 感谢您的更正,我会仔细阅读并纠正自己。
      • @GVH:如果您进行其他编辑,我将删除反对票。你已经很接近了——函数的堆栈框架、局部变量、参数和返回值都在堆栈上。
      • @kevingomes 是的,如果没有被编译器优化和删除,空函数会占用空间。 what in the function is occupying that memory as it is empty? 只是一个回报。只需按照@cnicutar 的方法进一步探索。但请记住,您将探索 machine 和 compiler 的特定行为,这可能在语言标准中未指定。
      【解决方案7】:

      是的,但是您可以将其声明为内联,因此编译器将获取源代码并将其移动到您调用该函数的任何位置。或者您也可以使用预处理器宏。虽然请记住使用内联会生成更大的代码,但它会执行得更快,并且编译器可以决定忽略您的内联请求,如果它觉得它会变得很大。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-10-21
        • 2011-02-17
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-06-04
        相关资源
        最近更新 更多