繁体   English   中英

堆栈是否在需要时分配更多空间,还是会溢出?

[英]Does a stack allocate more space if needed or does it overflow?

对于x86组装,假设我们有一个像这样的堆栈 在此处输入图片说明

堆栈为它拥有的2个局部变量分配了2个字。 但是,如果您强行将第三个局部变量推入堆栈,该怎么办? ESP是否向上移动以为变量留出空间,还是变量会覆盖ESP?

使用x86,将数据“推入”堆栈的指令还会修改堆栈指针(在本例中为%esp )以标记新的堆栈顶部。 “弹出”数据的指令沿相反方向修改堆栈指针。

在没有特殊推入和弹出指令的机器上,程序必须首先修改堆栈指针,然后将数据存储到堆栈中。

通常,为堆栈保留较大的空间。 堆栈指针仅标记当前正在使用的部分。 程序可以根据需要随意上下移动堆栈指针。

为堆栈保留的区域可能取决于操作系统和/或开发人员工具。 例如,在具有Apple开发人员工具的macOS上,默认堆栈大小为8兆字节,可以通过将“ -stack_size size ”切换到链接器( ld命令)进行更改。 (这是针对主堆栈的。使用多个线程的程序为每个创建的线程都有一个额外的堆栈。这些线程的堆栈大小是分别设置的。)

尽管为堆栈保留了大范围的虚拟地址空间,但操作系统可能不会在程序启动后立即将其全部映射到物理内存。 操作系统可能只映射一部分,然后随着堆栈增长到该区域映射更多的部分。

通常,超出堆栈的虚拟地址空间的某些部分保持未映射状态,因此尝试访问它会导致异常。 该区域中的地址空间页面称为保护页面。 因此,如果程序使堆栈超出保留区域并尝试将值写入未映射的保护页,则会发生异常,并且系统将报告堆栈溢出。

没有什么可以阻止程序写入为堆栈保留但略微超出堆栈指针的区域。 这将是一个错误,但是通常不会被硬件检测到。 此外,执行此操作的程序可能会正常运行一段时间。 它可以将数据存储到该区域并按预期加载回去。 但是,您在过程中还会发生其他通常不知道的事情。 例如,信号可能会传递给您的过程。 发生这种情况时,系统会中断程序的常规处理,将新数据压入堆栈,并调用信号处理程序例程。 当例程返回时,将从堆栈中删除数据,并且程序将恢复正常执行。 但是,如果您的程序已将数据存储在堆栈指针之外,则该数据现在消失了,因为它已被信号处理程序的数据覆盖。 因此,将数据存储在堆栈指针之外的程序在大多数情况下似乎都可以运行,但是在信号到达错误时刻的极少数情况下会失败。

(在某些系统上,堆栈的安全区域实际上是超出堆栈指针中地址的固定距离,而不是恰好位于该地址处。这个额外的安全空间可以称为“红色区域”。)

有关大多数详细信息,请参见@Eric的答案。

有些操作系统不仅懒得立即将整个保留的堆栈区域实际映射到物理页面,有些甚至根本没有逻辑映射它。 例如,在Linux上, /proc/self/maps中的堆栈映射的大小小于ulimit -s值。 但是触摸该区域中的内存将导致内核扩展映射(达到最大大小限制),即使它远低于当前映射的末尾。

这与通常的懒惰映射分开,在懒惰映射中,新分配的mmap(MAP_ANONYMOUS)区域的所有页面都在写时复制映射到了相同的物理页面(全部为零)。 因此,读取新页面可能会给您带来TLB丢失(要在该表中查找该虚拟地址的信息)和L1D高速缓存命中(因为所有仍然写的页面的物理地址都相同) 1

在其他操作系统(例如Windows)上,您不能一jump而就。 如果访问位于当前映射的最低地址页面的几个页面之内,则内核的页面错误处理程序将仅为您映射新的堆栈内存。 (它也可以检查ESP / RSP是否在故障地址之下)。 如果这些检查中的任何一个失败,页面错误都会导致程序异常,而不是通过静默映射该页面并重新运行装入或存储指令来处理。

这意味着在堆栈上为大型阵列分配空间必须在需要它的操作系统上每页左右探测堆栈 微软为其Visual Studio编译器提供的文档中包含一些详细信息: /Gs选项默认为/Gs4096 :当大量增加堆栈时,或每4k至少探测一次,或者对于可能大小那么大的可变大小的数组进行探测。

“探针”没什么特别的,只是加载或存储到堆栈地址以触发页面错误(如果尚未分配页面),因此堆栈一次增加一页而不是出错。


脚注

  1. malloccalloc使用mmap ,而calloc知道mmap会将页面清零,因此当它从内核获得新的内存时,它不会重做清零。

    但是C ++ std::vector太笨了(在gcc和clang中,使用libc++libstdc++ ),并且即使在new最终调用mmap大型分配中,也会弄脏所有新的内存本身。 C ++的可替换new意味着编译器/库只能通过-fwhole-program或链接时优化来对此进行优化,但是从理论上讲这是可能的。 由于实际上不会发生这种情况,因此,如果此行为有用(例如,仅写一部分的稀疏分配),则使用自定义分配器或避免使用std::vector

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM