来自Windows核心编程 – 第十六章
本章主要讲述内容是应用程序中线程栈的相关知识。
有时候系统会在用户进程的地址空间中预定区域。比如系统在分配进程环境块和线程环境块的时候,就会发生这种情况,还有一种情况就是 分配线程栈。
系统在创建线程时,会给线程栈预定一块地址空间区域,并给区域调拨一些物理存储器。
默认情况下,系统会预定 1MB 的地址空间并调拨两个页面的存储器。
构建应用程序时开发人员可以通过两种方式来改变默认值:
- 使用 MS C++ 编译器的 /F 选项 /Freserve
- 使用 MS C++ 链接器的 /STACK 选项 /STACK:reserve[,commit]
在构建应用程序时,链接器会把想要的栈的大小写入 .exe 或 .dll 文件的PE文件头中。当系统创建线程栈的时候,会根据PE头文件中的大小来预订地址空间区域。
要注意的是,当调用 CreateThread 或者 _beginthreadex 函数来创建线程时,开发人员可以指定需要在一开始就调拨的存储器数量。
可以将栈大小参数传递为 0,这样的话就会使用默认值 1MB,每次调拨一个物理存储器。
我们应该还记得这个知识点:
对于栈来讲,它的生长方向是向下的,是向着内存地址减小的方向增长。
所以栈顶应该是在高内存地址,预定了地址空间后,系统会给区域顶部(地址最高)的两个页面调拨物理存储器。这两个页面,一个是我们当前正在使用的页面,它的保护属性是 PAGE_READWRITE,下一个页面称为防护页面,它的保护属性标志是 PAGE_GUARD。
如果需要拓展空间时,系统会先给防护页面下面的页面调拨存储器,然后去除防护页面的保护属性标志,然后给新调拨的存储页指定保护属性标志。这样就能够在需要的时候再给线程栈分配物理存储器了。
要注意的是,系统永远不会给区域地址最低的页面调拨物理存储器。
当系统给区域地址最低的那个页面的前一个页面(倒数第二个页面)调拨物理存储器时,会执行一个额外的操作——抛出 EXCEPTION_STACK_OVERFLOW 异常,该异常对应的值为 0xC00000FD.通过使用结构化异常处理,系统会在发生这一情况时通知我们的程序,从而使程序能够以一种合适的方式从这一异常情况下恢复。
如果线程在引发栈溢出异常后继续使用栈,并用尽了倒数第二个页面,试图访问最后一个没有分配物理存储器的页面,那么此时系统会抛出访问违规异常。此时会弹出错误报告并结束进程。
然后还有一个栈下溢的概念…
简单来讲就是访问了超出栈的范围的地址。因为栈的分配是从高到低的。数组的头部在低内存地址。这样如果访问超出了数组的范围,那么就会导致栈的下溢。
而且这种情况很难检测出来…因为后一块内存可能有了别的数据结构,恰好对那块数据结构进行了修改,系统无法检测出这种破坏。
最后我还发现了一个好玩的事情,那就是int数组的分配是以16byte为单位的…其他的暂时还没有测试。
自己看着比较就好了…没用测double的…可能会有点不一样,指针和函数指针应该也会有些不一样。