如何主动打印调用栈?如果是Java、Js,那么很简单,三行就能实现。但 VisualStudio 就复杂多了。如果不下断点,那么只能在崩溃的时候被动查看。

而使用 Backward-Cpp ,只需在项目中拖入一个hpp文件,就可以主动打印。但默认输出是 stderr,无法在 VisualStudio 的 output 窗口看到任何信息。全网搜索半小时后,才从另外零星的代码片段中推得,需要将 stringstream 作为输出对象:

std::stringstream stream;

using namespace backward;
StackTrace st; st.load_here(32);
Printer p; p.print(st, stream);

然后打印 stream 内容至 VisualStudio 输出窗口:

::OutputDebugStringA(stream.str().c_str());

默认输出格式比较零散,下面分享一些强化的方法。

一、调整 Backward-Cpp 参数

backward.hpp为我们引入了 StackTrace 和 Printer, 这两个对象的参数都可以调整(开发者比较用心)。前者的构造参数是数字,代表打印深度,默认32层,一般8层即可。

StackTrace 还有一个有用的方法:skip_n_firsts(数字),指示跳过前面几层的打印,建议跳过两层,这样就可以自写util方法包绕,实现一行代码打印调用栈,然后直接从感兴趣的的方开始打印。

Printer 对象则可配置多个成员变量:

  1. 控制是否打印代码片段,建议关闭,因为开启后调用栈特别长,不利于定位上下游
    bool snippet = true;
  2. 控制是否逆序打印。默认逆序不用修改,和Java等同一个顺序(和Python不一样),这样就可以在打印调用栈前,输出一段关键字,然后调试运行,在 output 窗口搜索关键字,离关键字最近的下方就是最近的函数调用点了。
    bool reverse = true;
  3. lua 打印lua堆栈 如何打印堆栈_ide


二、魔改打印格式,适配VisualStudio

Backward-Cpp 的默认打印格式不是上图这样的,虽然可以很漂亮,但没有适配 VisualStudio,无法双击定位文件。文件名前面不能有非空字符,需要照着这个pull请求魔改:

os << "\n";//Need a newline or it does not work
    os << source_loc.filename << "(" << source_loc.line << "):";//This exact format is required
    os << " line "  << source_loc.line << ", in " << source_loc.function;
#else
	  os << indent << "Source \"" << source_loc.filename << "\", line "
		  << source_loc.line << ", in " << source_loc.function;

此外,我还去掉了 #序号 前缀,使得调用栈更加紧凑:

//os << "#" << std::left << std::setw(2) << trace.idx << std::right;

三、用 vhk 实现快捷刷新调试器

修改代码后,经常需要重新开始调试。若只有一个解决方案还好,但如果是多模块项目,比如重新编译duilib这一界面的静态库后,需要重新调试实际程序,于是需要以下两个重复步骤——

  1. 查找底部点击底部****毫无高亮效果的任务栏,切换至程序项目的窗口。
  2. 再点击VisualStudio顶部****小小的"Restart"按钮

"Restart"按钮默认快捷键是 ctrl+shift+f5,需要双手操作,很不方便。其实,F5按键默认是运行,但都处于调试状态了,为何不用F5直接“刷新”调试器?这样一键多用,岂不美哉!

可以用ahk拦截F5,然后根据 VisualStudio 标题是否含有(Running)子串分发不同的快捷键:

#IfWinActive ahk_exe devenv.exe
$F5::
	WinGetTitle,S
	;消息(S, "w450")
	StringGetPos, idx, S, (Running)
	if(idx > 0) {
		Send ^+{F5}
	} else {
		Send {F5} 
	}
return

更激进地,还可以右击左边框启动调试器:

(需要设置Debug.start快捷键为Ctrl+Shift+Alf+F5,Debug.start竟然和Debug.Restart相互冲突,不能设置为同一个,不知咋想的)

~RButton::
	MouseGetPos, xpos, ypos
	if(xpos < 10) {			
		SendInput ^+{F5}
		SendInput ^+!{F5}
	}
return