【问题标题】:Segmentation fault - why and how does it work?分段错误 - 为什么以及如何工作?
【发布时间】:2013-12-19 22:51:07
【问题描述】:

在下面定义的两个函数中,它都尝试在堆栈中分配 10M 的内存。但是分段错误只发生在第二种情况而不是第一种情况下,我试图理解为什么会这样。

函数定义1:

a(int *i)
{
    char iptr[50000000]; 
    *i = 1;
}

函数定义2:

a()
{
    char c;
    char iptr[5000000];

    printf("&c     = 0x%lx, iptr = 0x%x  ...  ", &c, iptr);
    fflush(stdout); 
    c = iptr[0]; 
    printf("ok\n");
}

根据我的理解,如果局部变量未动态分配内存,则存储在程序的堆栈部分中。所以我想,在编译期间,编译器本身会检查变量是否适合堆栈。

因此,如果上述情况属实,那么在这两种情况下都应该发生分段错误(即在情况 1 中也是如此)。

我从中选择的网站 (http://web.eecs.utk.edu/courses/spring2012/cs360/360/notes/Memory/lecture.html) 指出,当代码尝试将 iptr 压入堆栈以进行 printf 调用时,段错误发生在 a 的函数 2 中。这是因为堆栈指针指向 void。 如果我们没有在堆栈指针处引用任何东西,我们的程序应该可以运行。

我需要帮助来理解最后的陈述以及我之前对此的疑问。

【问题讨论】:

  • 你确定iptr 在第一种情况下没有被优化吗?你不使用它......关于“我们没有在堆栈指针上引用任何东西”的陈述只是说在堆栈上分配大量内存可能不会导致崩溃(因为“分配”通常包括只是将堆栈指针增加分配大小),但尝试使用堆栈会导致崩溃,因为它超过了实际保留堆栈空间的末尾。
  • 编译器可以随意放置变量,但不能保证局部变量不会被隐式堆分配。唯一的要求是编译器清理它们。
  • 我在Error accessing pointers when succesfully allocated array is too big中解释过,基本上它会调整堆栈指针,但如果它没有实际尝试使用它没有问题。
  • @ShafikYaghmour:感谢您指出这一点。但是这种变量到堆栈的映射是在编译时完成的,在这两种情况下都应该发生段错误,对吗??即使在第一种情况下没有访问试验但仍然完成映射......
  • @AadityaGavandalkar 在堆栈上至少为gcc 分配x86 将只是堆栈指针的sub,它不需要越界内存访问。这当然取决于具体的实现,但它很可能解释了您所看到的行为,但在不同平台上的工作方式可能不同,而且这不是您应该回复的行为。

标签: c++ c memory-management segmentation-fault


【解决方案1】:

我很惊讶没有人注意到第一个函数分配的空间是第二个函数的 10 倍。第一个函数的 5 后有七个零,而第二个函数的 5 后有六个零:-)

我用 gcc-4.6.3 编译它并在第一个函数上出现分段错误,但在第二个函数上没有。在我删除第一个函数中的附加零后,段错误消失了。在第二个函数中添加零引入了段错误。所以至少在我的情况下,这个段错误的原因是程序无法在堆栈上分配所需的空间。我很高兴听到与上述不同的观察结果。

【讨论】:

    【解决方案2】:

    printf 格式字符串假定指针、整数 (%x) 和长整数 (%lx) 大小相同;这在您的平台上可能是错误的,导致未定义的行为。请改用%p。我打算对此发表评论,但现在还不能。

    【讨论】:

      【解决方案3】:

      所以我想,在编译期间,编译器会检查变量是否适合堆栈。

      不,那是不可能的。编译函数时,编译器不知道调用该函数时的调用堆栈是什么,因此它会假设您知道自己在做什么(可能是也可能不是)。另请注意,堆栈空间量可能会受到编译时间和运行时限制的影响(在 Linux 中,您可以在启动进程的 shell 上使用 ulimit 设置堆栈大小)。

      我需要帮助来理解最后的陈述以及我之前对此的疑问。

      我不会尝试过多地研究该声明,它不是标准,而是基于特定实现的知识,甚至没有在那里描述,因此建立在一些假设之上不一定是真的。

      它假定分配数组的行为不会“触及”分配的内存(在某些实现中的某些调试版本中这是错误的),因此如果数据未被触及,您是否尝试分配 1 字节或 100M你的程序 分配fine -- 这不一定是这样。

      它还假设函数printf 的参数在堆栈中传递(由于函数的可变参数性质,我知道的所有实现中实际上都是这种情况)。使用前面的假设,数组会溢出堆栈(假设堆栈printf,参数的值将被推送到堆栈超出数组。这将写入到内存,并且写入将超出为堆栈分配的空间并崩溃。

      同样,所有这些都是实现,不是由语言定义的。

      【讨论】:

      • 感谢您的回答。您和 Shafik 的意见帮助我更好地理解。
      【解决方案4】:

      您的代码中的错误是由以下代码引发的:

      ; Find next lower page and probe
      cs20:
              sub     eax, _PAGESIZE_         ; decrease by PAGESIZE
              test    dword ptr [eax],eax     ; probe page. "**This line throws the error**"
              jmp     short cs10
      
      _chkstk endp
      
              end
      

      来自 chkstk.asm 文件,该文件提供对过程入口的堆栈检查。而这个文件明确定义:

      _PAGESIZE_      equ     1000h
      

      现在作为对您的问题的解释 This Question 告诉您所需要的一切:Shafik Yaghmour

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2016-06-08
        • 2019-01-17
        • 2011-03-20
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-03-17
        相关资源
        最近更新 更多