我最近使用了 /FAsu
Visual C++ 编译器选项 来输出一个特别长的成员函数定义的源代码和汇编信息。在汇编输出中,在堆栈帧设置之后,有一个调用神秘的 _chkstk()
函数。
MSDN 页面关于 _chkstk()
的文档没有解释为什么要调用这个函数。我还看到了 Stack Overflow 上的问题 分配大于一页大小的缓冲区到堆栈上会损坏内存?,但我不明白原帖和被接受的答案在说什么。
_chkstk()
CRT 函数的目的是什么?它是做什么的?
我最近使用了 /FAsu
Visual C++ 编译器选项 来输出一个特别长的成员函数定义的源代码和汇编信息。在汇编输出中,在堆栈帧设置之后,有一个调用神秘的 _chkstk()
函数。
MSDN 页面关于 _chkstk()
的文档没有解释为什么要调用这个函数。我还看到了 Stack Overflow 上的问题 分配大于一页大小的缓冲区到堆栈上会损坏内存?,但我不明白原帖和被接受的答案在说什么。
_chkstk()
CRT 函数的目的是什么?它是做什么的?
当线程使用额外的堆栈时,Windows在堆栈结尾映射一个无法访问的内存作为守护页 -- 如果程序尝试使用比当前映射的堆栈更多的堆栈,则会出现访问冲突。操作系统捕捉到这个错误后,在旧的守护页地址处映射另一页堆栈,创建新的守护页,并从导致错误的指令继续执行。
如果函数有多页本地变量,则其访问的第一个地址可能超出当前堆栈末尾超过一页。因此,它会跳过守护页并触发访问违规,而操作系统没有意识到需要更多的堆栈。如果所需的总堆栈特别巨大,甚至可能超出守护页、虚拟地址空间分配给堆栈的末尾,进入实际上已被用于其他用途的内存。
因此,_chkstk
确保有足够的空间来存储本地变量。可以想象它通过按页面大小的间隔依次触碰本地变量的内存(称为 "堆栈探测")来实现这一点,以确保不会错过守护页。我不知道它是否实际上这样做,可能采取更直接的方式并指示操作系统映射一定量的堆栈。无论哪种方式,如果所需的总堆栈大于虚拟地址空间可用于堆栈,则操作系统可以抱怨而不是执行未定义的操作。
_chkstk()
]只是从当前堆栈指针位置到请求分配的每4K内存地址进行访问"。感谢您详细解释的答案。 - Daniel Trebbien我查看了__chkstk
的代码,它确实会在一页间隔中进行重复堆栈探测。这样一来,就不需要向操作系统发起任何调用。 rax
参数是您要添加数据的大小。它确保目标地址(当前rsp
- rax
)是可访问的。如果rax
> rsp
,则为地址0执行此操作。有趣的是,它首先将地址与gs:[10h]
比较,后者是当前映射的最低页;如果目标地址>=此值,则不执行任何操作。
顺便说一下,至少对于64位代码,它的拼写是两个下划线:__chkstk__
。