【问题标题】:Allocating Memory to a recursive function将内存分配给递归函数
【发布时间】:2013-10-28 16:34:43
【问题描述】:

我写了一个如下的简单程序并跟踪它。

#include<stdio.h>
int foo(int i)
{
    int k=9;
    if(i==10)
            return 1;
    else
            foo(++i);
    open("1",1);
}
int main()
{
    foo(1);
}

我这样做的目的是检查如何为堆栈上的函数中的变量(在本例中为 int k)分配内存。我使用了一个开放的系统调用作为标记。 strace 的输出如下:

execve("./a.out", ["./a.out"], [/* 25 vars */]) = 0
brk(0)                                  = 0x8653000
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or            directory)
mmap2(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) =     0xb777e000
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat64(3, {st_mode=S_IFREG|0644, st_size=95172, ...}) = 0
mmap2(NULL, 95172, PROT_READ, MAP_PRIVATE, 3, 0) = 0xb7766000
close(3)                                = 0
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or     directory)
open("/lib/i386-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0000\226\1\0004\0\0\0"..., 512) = 512
fstat64(3, {st_mode=S_IFREG|0755, st_size=1734120, ...}) = 0
mmap2(NULL, 1743580, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) =     0xb75bc000
mmap2(0xb7760000, 12288, PROT_READ|PROT_WRITE,     MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1a4) = 0xb7760000
mmap2(0xb7763000, 10972, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0xb7763000
close(3)                                = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) =     0xb75bb000
set_thread_area({entry_number:-1 -> 6, base_addr:0xb75bb900, limit:1048575, seg_32bit:1, contents:0, read_exec_only:0, limit_in_pages:1, seg_not_present:0, useable:1}) = 0
mprotect(0xb7760000, 8192, PROT_READ)   = 0
mprotect(0x8049000, 4096, PROT_READ)    = 0
mprotect(0xb77a1000, 4096, PROT_READ)   = 0
munmap(0xb7766000, 95172)               = 0
open("1", O_WRONLY)                     = -1 ENOENT (No such file or     directory)
open("1", O_WRONLY)                     = -1 ENOENT (No such file or     directory)
open("1", O_WRONLY)                     = -1 ENOENT (No such file or directory)
open("1", O_WRONLY)                     = -1 ENOENT (No such file or     directory)
open("1", O_WRONLY)                     = -1 ENOENT (No such file or     directory)
open("1", O_WRONLY)                     = -1 ENOENT (No such file or     directory)
open("1", O_WRONLY)                     = -1 ENOENT (No such file or     directory)
open("1", O_WRONLY)                     = -1 ENOENT (No such file or directory)
open("1", O_WRONLY)                     = -1 ENOENT (No such file or directory)
exit_group(-1)                          = ?

在 strace 输出的末尾,您可以看到在打开的系统调用之间没有调用任何系统调用。那么,在没有系统调用的情况下,对于被调用的函数,内存是如何分配到堆栈上的呢?

【问题讨论】:

  • foo 定义缺少“}”?此外,编译优化器将删除对 k 的引用,因为它没有在任何地方使用。
  • 堆栈操作(新堆栈帧、从函数返回、为新变量调整空间、删除变量等)通常不是通过系统调用完成的,而是由生成的代码简单地处理。因此,strace 不会很有用...
  • 这里没有回答你的问题,但值得注意的是你的程序并没有像你想象的那样运行:即使每次递归调用都为堆栈分配了内存,内存也会全部分配在您进行第一次 open() 调用时,因为您的递归发生在调用之前,而不是之后。
  • 关于strace 的严肃说明:strace 无法显示以dl_open() 打开的库

标签: linux memory c function


【解决方案1】:

主线程的堆栈内存由内核在execve() 系统调用期间分配。在此调用期间,还设置了可执行文件中定义的其他映射(也可能用于可执行文件中指定的动态链接器)。对于 ELF 文件,这是在 fs/binfmt_elf.c 中完成的。

其他线程的堆栈内存由线程支持库mmap()ed,它通常是 C 运行时库的一部分。

您还应该注意,在虚拟内存系统上,内核会增加主线程堆栈以响应页面错误,直至达到可配置的限制(如ulimit -s 所示)。

【讨论】:

    【解决方案2】:

    您的(单线程)程序堆栈大小是固定的,因此无需再进行分配。

    您可以使用ulimit -s 命令查询和增加此大小。

    请注意,即使您将此限制设置为“无限制”,也总会有一个实际限制:

    • 对于 32 位进程,除非您的 RAM/swap 不足,否则虚拟内存空间限制会导致地址冲突

    • 对于 64 位进程,内存(RAM + 交换)耗尽会破坏您的系统并最终导致程序崩溃。

    无论如何,从来没有明确的系统调用会增加堆栈大小,它只在程序启动时设置。

    还请注意,堆栈内存的处理方式与堆内存完全相同,即只有已访问的部分映射到实际内存(RAM 或交换)。这意味着堆栈会按需增长,但除了标准虚拟内存管理之外没有其他机制可以处理这种情况。

    【讨论】:

    • 我看不出它是如何以任何方式修复的。你在哪里看到的?
    • 这是设计使然。当程序启动时,主线程(或此处为单个线程)的堆栈大小具有固定大小。您可以使用“ulimit -s”命令查看它是什么。这是虚拟内存,就像使用 malloc 获取的内存一样,它只是保留,并且只有在访问时才由真实内存(RAM 或交换)支持。没有机制允许进程在运行时增加其堆栈大小。
    • 您可以设置ulimit -s unlimited。在那之后,你没有真正的限制。你可以测试this code,看看它是如何前进和前进的,堆栈是如何向更小的地址增长的。你会看到,在运行了很长时间之后,它最终会如何与程序的其他部分发生冲突。但是由于堆栈从bf82f000 开始(确切值可能会发生变化)并且上述其他部分在401bb000 及以下,我们在堆栈上有很大的空间可以使用(我在它碰撞之前已经达到了步骤1027625) . ulimit -s 只是一个额外的“噱头”……
    • 顺便说一句,有趣的是,如果程序达到(设置)ulimit,我会得到一个 SIGSEGV,而如果我点击“程序区域”,我会得到一个 SIGBUS...跨度>
    • @glglgl 即使指定“无限制”,您也总是遇到实际限制。如果你的程序是用 64 位编译的,不会有地址冲突但内存耗尽。无论如何,从来没有一个显式的系统调用来执行 OP 正在寻找的分配。
    【解决方案3】:

    堆栈使用和分配(至少在 Linux 上)以这种方式工作:

    • 分配了一点堆栈。
    • 在程序的“其他”部分之后设置保护范围,大约为地址空间的 1/4。
    • 如果堆栈用完了,堆栈会自动增加。
    • 如果达到ulimit 限制(和SIGSEGVs),或者如果不存在这样的限制,直到达到保护范围(然后获得SIGBUS),就会发生这种情况。

    【讨论】:

    • @PaulDaviesC:它是在页面错误时完成的。例如在 x86 上,首先检查所有其他可能性,如果它们都不是,则页面错误是由于当前映射的堆栈之外的访问(之前的检查是 address + 65536 + 32 * sizeof(unsigned long) &lt; regs-&gt;sp)。见arch/x86/mm/fault.c:do_page_fault()
    【解决方案4】:

    在递归“触底”之前,您的程序不会开始进行任何 open 调用。此时,堆栈被分配,它刚刚从嵌套中弹出。

    为什么不使用调试器逐步完成它。

    【讨论】:

      【解决方案5】:

      您想知道变量分配给为函数创建的“堆栈帧”的位置吗? 我已经修改了你的程序,向你展示了你的堆栈变量 k 的内存地址,以及一个参数变量 kk,

      //Show stack location for a variable, k
      #include <stdio.h>
      int foo(int i)
      {
          int k=9;
          if(i>=10) //relax the condition, safer
              return 1;
          else
              foo(++i);
          open("1",1);
          //return i;
      }
      int bar(int kk, int i)
      {
          int k=9;
          printf("&k: %x, &kk: %x\n",&k,&kk); //address variable on stack, parameter
          if(i<10) //relax the condition, safer
              bar(k,++i);
          else
              return 1;
          return k;
      }
      int main()
      {
          //foo(1);
          bar(0,1);
      }
      

      我的系统上的输出

      $ ./foo
      &k: bfa8064c, &kk: bfa80660
      &k: bfa8061c, &kk: bfa80630
      &k: bfa805ec, &kk: bfa80600
      &k: bfa805bc, &kk: bfa805d0
      &k: bfa8058c, &kk: bfa805a0
      &k: bfa8055c, &kk: bfa80570
      &k: bfa8052c, &kk: bfa80540
      &k: bfa804fc, &kk: bfa80510
      &k: bfa804cc, &kk: bfa804e0
      &k: bfa8049c, &kk: bfa804b0
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2016-07-28
        • 1970-01-01
        • 1970-01-01
        • 2015-02-09
        • 1970-01-01
        • 2016-01-04
        • 2014-01-02
        相关资源
        最近更新 更多