必须按线程粒度分别采集栈用量:linux通过/proc/self/task/tid/status读stkgrow(kb,内核4.12+支持,需syscall(sys_gettid)获tid);windows用getthreadstackinfo计算highlimit与current差值(仅限当前线程,win10 1809+);跨平台可采样栈指针粗略估算。

多线程下无法靠 main 入口或单个 /proc/self/status 获取各线程栈用量,必须按线程粒度分别采集,且 Linux 与 Windows 的接口逻辑、精度和可用性差异极大。
Linux 下读取 /proc/self/task/TID/status 获取 StkGrow
每个线程在内核中对应一个 task 目录,/proc/self/task/<code>TID/status 中的 StkGrow 字段(单位 KB)表示该线程已实际使用的栈空间(按页统计的近似值),StkSize 是分配总量。这个字段从内核 4.12 起稳定支持,旧版本可能为空。
- 获取当前线程 TID 必须用
syscall(SYS_gettid),pthread_self()返回的是pthread_t,不能直接拼进路径 - 读取前需检查
open("/proc/self/task/12345/status", O_RDONLY)是否返回ENOENT,TID 可能已退出 -
StkGrow是内核按 page fault 统计的,不反映函数调用帧的精确字节占用,比如一次大数组局部变量分配可能只触发一次增长但实际占数 KB - 频繁读取会触发大量
stat()和文件打开,建议采样间隔 ≥ 100ms,避免干扰被监控线程
Windows 下用 GetThreadStackInfo 估算已用栈
GetThreadStackInfo 是目前 Windows 上唯一能拿到当前线程栈边界信息的公开 API,但它只对调用线程自身有效,传入其他线程句柄会直接失败并返回 ERROR_INVALID_PARAMETER。
- 必须链接
Kernel32.lib,且最低目标平台为 Windows 10 1809(RS5),Win7/Win8 不支持 - 返回的
Current字段是当前rsp值,但编译器开启帧指针省略(/Oy或-fomit-frame-pointer)时,它可能比真实栈顶高几个字节(因未保存 rbp) - 计算方式为
(char*)info.HighLimit - (char*)info.Current,注意不要反向相减,否则结果溢出为极大正数 - 该值包含当前函数调用开销(如参数压栈、返回地址),不是纯“用户变量”占用,但比采样局部变量地址更稳定
跨平台采样栈指针做轻量估算
当无法依赖系统 API(如嵌入式、旧 OS、沙箱环境),最可行的方式是手动记录栈帧地址差值。栈向下增长,所以越深的函数调用,其栈帧地址数值越小。
- 用
__builtin_frame_address(0)(GCC/Clang)或_AddressOfReturnAddress()(MSVC)比取&local_var更可靠——后者可能被优化到寄存器,而前者强制读取当前帧基址 - 主线程可在
main开头记一次初始栈底:static const void* g_main_stack_bottom = __builtin_frame_address(0),后续线程用各自首次调用点记录 - 线程内任意位置调用
(char*)g_main_stack_bottom - (char*)__builtin_frame_address(0)即为粗略已用字节数,误差在百字节级,足够用于告警阈值判断 - 该方法不依赖内核版本或权限,但无法区分“已用”和“已分配但未访问”的页,也不能反映栈保护页(guard page)是否被触达
真正棘手的不是怎么读,而是读到的数字代表什么:Linux 的 StkGrow 是内核视角的页增长次数,Windows 的 Current 是硬件寄存器快照,而采样指针只是两个时间点的地址差——三者单位、语义、更新时机全都不一致。选哪种方式,得先明确你要回答的问题:是防栈溢出(看是否逼近上限),还是分析栈内存浪费(看长期驻留深度),还是调试递归失控(需毫秒级变化趋势)。没有通用解,只有场景适配。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











