Visual Studio 调试技巧合集
Visual Studio 调试技巧合集
几个在大代码库里查 bug 时常用、但容易被忽略的 VS 调试手段:数据断点定位内存被谁改的、诊断工具抓内存泄漏、看 std::mutex 被哪个线程锁住,以及远程调试和临时关函数优化。
数据断点:抓「这块内存是谁改的」
数据断点(Data Breakpoint)监视一块内存,一旦它被写就触发中断。它特别适合两类问题:在庞大代码库里查某个值被意外改写的逻辑 bug,以及定位内存破坏——直接告诉你哪一行代码写花了这块内存。
前提是先确定要监视的内存地址,通常是某个变量。下面用一个真实案例走一遍流程:角色会被墙卡住。
- 复现问题。 先稳定复现卡住的现象。
- 用调试工具对比正常 / 异常状态。 对比发现:碰撞球(collision sphere)的位置没有跟着角色走,但又没有真正发生碰撞(碰撞发生时本应高亮)。所以要盯的是碰撞球的位置。
- 找到控制位置的变量。 定位到控制角色位置的变量(这里是个
m_Trans,一个 vector4),在它身上准备下断点。 - 创建数据断点。
- 用这个变量的地址创建数据断点;
m_Trans是 vector4,监视宽度选 8 字节(按需要覆盖的字段大小定);- 把其他断点都删掉,只留这个数据断点,避免噪声。
- 分析触发数据变化的代码。 继续运行,每次这块内存被写都会断下来。逐个看调用栈:
- 代码块 1:碰撞发生时会修正位置;
- 代码块 2:会修改位置。
- 问题在于:所有对位置的修改都应该发生在位置修正之前。现在顺序反了,修正先跑、修改后跑,于是修正被覆盖,角色卡住。
- 修复并测试。 把代码块 2 移到代码块 1 之前,问题消失。
数据断点的价值就在第 5 步——不用通读代码猜逻辑,让 CPU 硬件断点直接把每个写这块内存的现场和调用栈摆到你面前。
注意硬件数据断点数量有限(x86/x64 通常 4 个),且监视宽度受限(1/2/4/8 字节);超出范围 VS 会退化成软件断点,慢很多。
诊断工具:定位内存泄漏
VS 自带的 Diagnostic Tools 抓内存泄漏很顺手,能直接告诉你泄漏的内存是在哪分配的。流程:
先通过 Process Memory 曲线确认现象——比如发现每次关卡重载后内存只增不减。

在每次关卡重载的时间点各打一个 Snapshot。

对比快照,能看到某类对象的分配计数持续增长。

双击调用栈直接跳到分配代码。
在那分析这块内存为什么没被释放。
关键是用快照差异而不是看绝对值:两个稳态时间点之间多出来的、没被回收的分配,才是泄漏的嫌疑。
看 std::mutex 被哪个线程锁住
排查死锁时常想知道某个 std::mutex 当前是被哪个线程持有的。VS 默认的可视化器不显示这个,可以放一个 mutex.natvis 到:
/Documents/Visual Studio 2017/Visualizers/mutex.natvis
(对应你的 VS 版本目录)。之后在 watch 窗口展开 std::mutex 就能看到持有它的线程信息。具体的 natvis 写法见 这个 StackOverflow 回答。
远程调试另一台机器上的程序
要调另一台机器上的程序(比如一个独占全屏的游戏,本机调试器会被它顶掉),用 Remote Windows Debugger:在目标机上跑对应 VS 版本的 Remote Tools(从微软官网下 Remote Tools for Visual Studio),本机 Debug → Attach to Process 填目标机的远程连接地址即可。详见 Remote Debugging。
临时关掉某个函数的优化
Release 下断点跳来跳去、变量被优化掉看不了值时,可以只对某个函数关优化,而不用整个工程切 Debug:
#pragma optimize("", off)
void FunctionIWantToDebug()
{
// 这里的代码不会被优化,断点和变量都正常
}
#pragma optimize("", on)
调完记得把 pragma 删掉,别带进提交。