【问题标题】:creating large memory in stack在堆栈中创建大内存
【发布时间】:2014-02-27 22:25:59
【问题描述】:

我试图在堆栈中创建大量内存 (int a[1000000000])。它编译成功。但是在运行时它发生了Segfaulted。程序段错误是完全可以接受的。我的问题是,为什么编译器不能事先检测到并抛出编译错误?为什么要等到程序运行?

另外,当我分配int a[100] 时,asm 代码如下所示。

00000000004004b4 <main>:
  4004b4:       55                      push   %rbp
  4004b5:       48 89 e5                mov    %rsp,%rbp
  4004b8:       48 81 ec 18 01 00 00    sub    $0x118,%rsp (memory created for 100 bytes)

如果我为大内存创建int a[1000000000],asm 代码看起来不同。

00000000004004b4 <main>:
  4004b4:       55                      push   %rbp
  4004b5:       48 89 e5                mov    %rsp,%rbp
  4004b8:       49 bb 78 00 17 be 33    movabs $0xfffe9433be170078,%r11
  4004bf:       94 fe ff 
  4004c2:       4c 01 dc                add    %r11,%rsp

有人可以解释一下吗?即为什么编译器不处理这个?

谢谢

孩子

【问题讨论】:

  • 编译器应该如何知道目标的内存量?
  • 我相信你刚刚超越了 Stack Segment。
  • 编译器如何知道运行时有多少堆栈空间可用?
  • 如果您有两个问题,那么请发布两个问题。不要发布一个有两个问题的问题;第二个问题很可能被忽略。如果不是,则很难选择哪个是公认的答案,是否应该在单独的答案中回答这两个问题。

标签: c gcc compiler-construction


【解决方案1】:

为什么编译器不能事先检测到这一点并产生编译错误或警告?

这个问题假设了一个不正确的前提。 技术上几乎没有阻止编译器编写者这样做。您可能希望它是一个警告而不是错误,但是编译器编写者可以很容易地为函数使用的本地存储总量设置一个不合理的巨大大小的阈值,并在该大小为时发出警告超过了。

由于您的问题基于错误的前提,因此没有答案。问一个更好的问题。

好的,如果是可能那么为什么编译器编写者实现这个警告?

无论您的编译器是开源还是闭源,无论开发它的人是付费的还是志愿者,对软件的所有更改都需要时间努力。两者都供不应求。

编译器警告是特性,因此必须进行设计、指定、实施、测试、调试、交付和维护。所有这些东西在时间和精力上都是昂贵的,并且所有这些都其他功能中删除时间和精力。

如果您喜欢的编译器中不存在此功能,那是因为该编译器的开发人员有大约 100 万件更好的事情要做。在这 100 万件更好的事情中,今年他们可能会设法实施几十个。如果您想提倡您的特定功能建议从目前的低位上移,请开始致电编译器开发人员并向他们说明这是他们可以花费精力的最佳方式 - 为明显虚假且会立即崩溃的条件创建警告。你可能不会走得太远。

在考虑要实现哪些编译器警告时,像我这样的编译器编写者会认真考虑要向编译器添加哪些警告。良好的警告可以识别以下情况:

  • 合法;非法代码当然是错误。
  • 可检测,误报率低;指示开发人员将正确、有效的代码更改为损坏代码的警告是错误的警告
  • 误导;所说的程序员应该相信代码做一件事,而实际上它做另一件事
  • 合理;程序员应该有理由在业务线程序中输入警告代码
  • 通过简单的测试无法检测到;该程序应该正常运行并做一些微妙的错误事情。如果程序立即崩溃,那么警告只是在测试发现之前几秒钟就发现了错误,这不是很好的价值。

您的提案符合前两个条件。它可以说符合第三个。毫无疑问,它不符合第四和第五个标准。因此,如果我是编译器开发人员,我会拒绝您的建议。我会花费我宝贵的时间和精力来添加成本更高的功能。

【讨论】:

    【解决方案2】:

    编译器不负责堆栈分配。二进制文件的堆栈空间由链接器保留。例如,ld--stack 选项:

    --堆栈预留

    --堆栈保留,提交

       Specify the number of bytes of memory to reserve (and optionally commit)
       to be used as stack for this program.  The default is 2Mb reserved,
       4K committed.  [This option is specific to the i386 PE targeted
       port of the linker]
    

    此外,进程可能会克隆自己以创建其他进程/线程。那时,创建克隆的人控制为堆栈分配多少内存,编译器和链接器都无法提供帮助。

    最后,操作系统可能还有其他限制。例如,Linux 对堆栈大小有软硬限制,如果违反为进程设置的限制,则不允许任何进程创建其堆栈。见ulimit

    理论上,编译器可以分析程序并尝试估计总堆栈使用量,例如,提示链接器应该尝试为堆栈保留多少。但是,例如,如果使用动态链接,这将变得非常成问题。或者,例如,程序有一个递归,其深度取决于运行时变量(即一些图搜索算法)。那么没有办法做到这一点。鉴于在编译器中实现这样的启发式方法绝非易事,而且大多数程序都有某种递归或动态链接这一事实,因此根本不值得付出努力。

    【讨论】:

      【解决方案3】:

      编译器无法知道所有目标机器的限制

      编译器任务只是为你的 CPU/架构编译你的机器码等效形式的代码

      您是否见过编译器抱怨堆中分配的内存过多? :-)

      编译器转码低级机器代码等效的高级程序

      【讨论】:

      • 谢谢。我认为编译器会知道代码运行的架构
      • 显然,如果编程正确,它可能是,但它不仅仅是一个编译器。编译器只是将您的高级程序转码为相应的低级机器表示。仅此而已。您想要的远远超出语法正确性
      • 等等:让我们想象在堆栈中分配一个缓冲区,该缓冲区的大小是在运行时计算的。编译器如何仅分析静态源代码就可能检测到堆栈溢出(!!!是的,stackoverflow!)?
      • 为了说明,您可以使用ulimit -s 更改堆栈大小。如果您通过首先运行ulimit -s 4294967296(4gig 堆栈)来设置 4gig 堆栈,您的程序将运行(假设系统上的 int 为 4 个字节)。
      【解决方案4】:

      这是为了使其在具有相同架构但不同规格的机器之间更加兼容。

      因为编译器无法预测您将在其上运行二进制文件的机器的规格。当您运行二进制文件时,它必须是类似的架构(x86/ARM),但具有相同架构的机器可以有不同的内存规格。

      【讨论】:

        猜你喜欢
        • 2011-11-07
        • 2017-06-15
        • 1970-01-01
        • 2014-11-12
        • 2012-09-06
        • 1970-01-01
        • 2023-03-03
        • 2012-09-23
        相关资源
        最近更新 更多