简单介绍
  调试是程序开发者必备技巧。如果不会调试,自己写的程序一旦出问题,往往无从下手。本人总结10年使用VC经验,对调试技巧做一个粗浅的介绍。希望对大家有所帮助。
  
  今天简单的介绍介绍调用堆栈。调用堆栈在我的专栏的文章VC调试入门提了一下,但是没有详细介绍。
  
  首先介绍一下什么叫调用堆栈:假设我们有几个函数,分别是function1,function2,function3,funtion4,且function1调用function2,function2调用function3,function3调用function4。在function4运行过程中,我们可以从线程当前堆栈中了解到调用他的那几个函数分别是谁。把函数的顺序关系看,function4、function3、function2、function1呈现出一种“堆栈”的特征,最后被调用的函数出现在最上方。因此称呼这种关系为调用堆栈(call stack)。
  
  当故障发生时,如果程序被中断,我们基本上只可以看到最后出错的函数。利用call stack,我们可以知道当出错函数被谁调用的时候出错。这样一层层的看上去,有时可以猜测出错误的原因。常见的这种中断时ASSERT宏导致的中断。
  
  在程序被中断时,debug工具条的右侧倒数第二个按钮一般是call stack按钮,这个按钮被按下后,你就可以看到当前的调用堆栈。
  
  实例一:介绍
  我们首先演示一下调用堆栈。首先我们创建一个名为Debug的对话框工程。工程创建好以后,双击OK按钮创建消息映射函数,并添加如下代码:
  
  void CDebugDlg::OnOK()
  {
  
  // TODO: Add extra validation here
  ASSERT(FALSE);
  
  }
  
  我们按F5开始调试程序。程序运行后,点击OK按钮,程序就会被中断。这时查看call stack窗口,就会发现内容如下:
  
  CDebugDlg::OnOK() line 176 + 34 bytes
  _AfxDispatchCmdMsg(CCmdTarget * 0x0012fe74 {CDebugDlg}, unsigned int 1, int 0, void (void)* 0x5f402a00 `vcall'(void), void * 0x00000000, unsigned int 12, AFX_CMDHANDLERINFO * 0x00000000) line 88
  CCmdTarget::OnCmdMsg(unsigned int 1, int 0, void * 0x00000000, AFX_CMDHANDLERINFO * 0x00000000) line 302 + 39 bytes
  CDialog::OnCmdMsg(unsigned int 1, int 0, void * 0x00000000, AFX_CMDHANDLERINFO * 0x00000000) line 97 + 24 bytes
  CWnd::OnCommand(unsigned int 1, long 656988) line 2088
  CWnd::OnWndMsg(unsigned int 273, unsigned int 1, long 656988, long * 0x0012f83c) line 1597 + 28 bytes
  CWnd::WindowProc(unsigned int 273, unsigned int 1, long 656988) line 1585 + 30 bytes
  AfxCallWndProc(CWnd * 0x0012fe74 {CDebugDlg hWnd=???}, HWND__ * 0x001204b0, unsigned int 273, unsigned int 1, long 656988) line 215 + 26 bytes
  AfxWndProc(HWND__ * 0x001204b0, unsigned int 273, unsigned int 1, long 656988) line 368
  AfxWndProcBase(HWND__ * 0x001204b0, unsigned int 273, unsigned int 1, long 656988) line 220 + 21 bytes
  USER32! 77d48709()
  USER32! 77d487eb()
  USER32! 77d4b368()
  USER32! 77d4b3b4()
  NTDLL! 7c90eae3()
  USER32! 77d4b7ab()
  USER32! 77d7fc9d()
  USER32! 77d76530()
  USER32! 77d58386()
  USER32! 77d5887a()
  USER32! 77d48709()
  USER32! 77d487eb()
  USER32! 77d489a5()
  USER32! 77d489e8()
  USER32! 77d6e819()
  USER32! 77d65ce2()
  CWnd::IsDialogMessageA(tagMSG * 0x004167d8 {msg=0x00000202 wp=0x00000000 lp=0x000f001c}) line 182
  CWnd::PreTranslateInput(tagMSG * 0x004167d8 {msg=0x00000202 wp=0x00000000 lp=0x000f001c}) line 3424
  CDialog::PreTranslateMessage(tagMSG * 0x004167d8 {msg=0x00000202 wp=0x00000000 lp=0x000f001c}) line 92
  CWnd::WalkPreTranslateTree(HWND__ * 0x001204b0, tagMSG * 0x004167d8 {msg=0x00000202 wp=0x00000000 lp=0x000f001c}) line 2667 + 18 bytes
  CWinThread::PreTranslateMessage(tagMSG * 0x004167d8 {msg=0x00000202 wp=0x00000000 lp=0x000f001c}) line 665 + 18 bytes
  CWinThread::PumpMessage() line 841 + 30 bytes
  CWnd::RunModalLoop(unsigned long 4) line 3478 + 19 bytes
  CDialog::DoModal() line 536 + 12 bytes
  CDebugApp::InitInstance() line 59 + 8 bytes
  AfxWinMain(HINSTANCE__ * 0x00400000, HINSTANCE__ * 0x00000000, char * 0x00141f00, int 1) line 39 + 11 bytes
  WinMain(HINSTANCE__ * 0x00400000, HINSTANCE__ * 0x00000000, char * 0x00141f00, int 1) line 30
  WinMainCRTStartup() line 330 + 54 bytes
  KERNEL32! 7c816d4f()
  
  这里,CDebugDialog::OnOK作为整个调用链中最后被调用的函数出现在call stack的最上方,而内核中程序的启动函数Kernel32! 7c816d4f()则作为栈底出现在最下方。
  
  实例二:学习处理方法
  微软提供了MDI/SDI模型提供文档处理的建议结构。有些时候,大家希望控制某个环节。例如,我们希望弹出自己的打开文件对话框,但是并不想自己实现整个文档的打开过程,而更愿意MFC完成其他部分的工作。可是,我们并不清楚MFC是怎么处理文档的,也不清楚如何插入自定义代码。
  
  幸运的是,我们知道当一个文档被打开以后,系统会调用CDocument派生类的Serialize函数,我们可以利用这一点来跟踪MFC的处理过程。
  
  我们首先创建一个缺省的SDI工程Test1,并在CTest1Doc::Serialize函数的开头增加一个断点,运行程序,并打开一个文件。这时,我们可以看到调用堆栈是(我只截取了感兴趣的一段):
  
  CTest1Doc::Serialize(CArchive & {...}) line 66
  CDocument::OnOpenDocument(const char * 0x0012f54c) line 714
  CSingleDocTemplate::OpenDocumentFile(const char * 0x0012f54c, int 1) line 168 + 15 bytes
  CDocManager::OpenDocumentFile(const char * 0x0042241c) line 953
  CWinApp::OpenDocumentFile(const char * 0x0042241c) line 93
  CDocManager::OnFileOpen() line 841
  CWinApp::OnFileOpen() line 37
  _AfxDispatchCmdMsg(CCmdTarget * 0x004177f0 class CTest1App theApp, unsigned int 57601, int 0, void (void)* 0x00402898 CWinApp::OnFileOpen, void * 0x00000000, unsigned int 12, AFX_CMDHANDLERINFO * 0x00000000) line 88
  CCmdTarget::OnCmdMsg(unsigned int 57601, int 0, void * 0x00000000, AFX_CMDHANDLERINFO * 0x00000000) line 302 + 39 bytes
  CFrameWnd::OnCmdMsg(unsigned int 57601, int 0, void * 0x00000000, AFX_CMDHANDLERINFO * 0x00000000) line 899 + 33 bytes
  CWnd::OnCommand(unsigned int 57601, long 132158) line 2088
  CFrameWnd::OnCommand(unsigned int 57601, long 132158) line 317
  
  
  从上面的调用堆栈看,这个过程由一个WM_COMMAND消息触发(因为我们用菜单打开文件),由CWinApp::OnFileOpen最先开始实际处理过程,这个函数调用CDocManager::OnFileOpen打开文档。
  
  我们首先双击CWinApp::OnFileOpen() line 37打开CWinApp::OnFileOpen,它的处理过程是:
  
   ASSERT(m_pDocManager != NULL);
   m_pDocManager->OnFileOpen();
  
  m_pDocManager是一个CDocManager类的实例指针,我们双击CDocManager::OnFileOpen行,看该函数的实现:
  
  void CDocManager::OnFileOpen()
  {
   // prompt the user (with all document templates)
   CString newName;
   if (!DoPromptFileName(newName, AFX_IDS_OPENFILE,
   OFN_HIDEREADONLY | OFN_FILEMUSTEXIST, TRUE, NULL))
   return; // open cancelled
   AfxGetApp()->OpenDocumentFile(newName);
   // if returns NULL, the user has already been alerted
  }
  
  很显然,该函数首先调用DoPromptFileName函数来获得一个文件名,然后在继续后续的打开过程。
  
  顺这这个线索下去,我们一定能找到插入我们文件打开对话框的位置。由于这不是我们研究的重点,后续的分析我就不再详述。
  
  实例三:内存访问越界
  在Debug版本的VC程序中,程序会给每块new出来的内存,预留几个字节作为越界检测之用。在释放内存时,系统会检查这几个字节,判断是否有内存访问越界的可能。
  
  我们借用前一个实例程序,在CTest1App::InitInstance的开头添加以下几行代码:
  
   char * p = new char[10];
   memset(p,0,100);
   delete []p;
   return FALSE;
  
  很显然,这段代码申请了10字节内存,但是使用了100字节。我们在memset(p,0,100);这行加一个断点,然后执行程序,断点到达后,我们观察p指向的内存的值(利用Debug工具条的Memory功能),可以发现它的值是:
  
   CD CD CD CD CD CD CD CD
   CD CD FD FD FD FD FD FD
   00 00 00 00 00 00 00 00
   ......
  
  根据经验,p实际被分配了16个字节,后6个字节用于保护。我们按F5全速执行程序,会发现如下的错误信息被弹出:
  
   Debug Error!
   Program: c:\temp\test1\Debug\test1.exe
   DAMAGE: after normal block (#55) at 0x00421AB0
   Press Retry to debug the application
  
  该信息提示,在正常内存块0x00421AB0后的内存被破坏(内存访问越界),我们点击Retry进入调试状态,发现调用堆栈是:
  
  _free_dbg_lk(void * 0x00421ab0, int 1) line 1033 + 60 bytes
  _free_dbg(void * 0x00421ab0, int 1) line 970 + 13 bytes
  operator delete(void * 0x00421ab0) line 351 + 12 bytes
  CTest1App::InitInstance() line 54 + 15 bytes
  
  很显然,这个错误是在调用delete时遇到的,出现在CTest1App::InitInstance() line 54 + 15 bytes之处。我们很容易根据这个信息找到,是在释放哪块内存时出现问题,之后,我们只需要根据这个内存的访问过程确定哪儿出错,这将大大降低调试的难度。
  
  实例四:子类化
  子类化是我们修改一个现有控件实现新功能的常用方法,我们借用实例一中的Debug对话框工程来演示我过去学习子类化的一个故事。我们创建一个缺省的名为Debug的对话框工程,并按照下列步骤进行实例化:
  
  在对话框资源中增加一个Edit控件
  用class wizard为CEdit派生一个类CMyEdit(由于今天不关心子类化的具体细节,因此这个类不作任何修改)
  为Edit控件,增加一个控件类型变量m_edit,其类型为CMyEdit
  在OnInitDialog中增加如下语句:
  
  m_edit.SubclassDlgItem(IDC_EDIT1,this);
  
  我们运行这个程序,会遇到这样的错误:
  
  
  Debug Assertion Failed!
  Application:C:\temp\Debug\Debug\Debug.exe
  File:Wincore.cpp
  Line:311
  
  For information on how your program can cause an assertion failure, see Visual C++ documentation on asserts.
  
  (Press Retry to debug the application)
  
  点击Retry进入调试状态,我们可以看到调用堆栈为:
  
  CWnd::Attach(HWND__ * 0x000205a8) line 311 + 28 bytes
  CWnd::SubclassWindow(HWND__ * 0x000205a8) line 3845 + 12 bytes
  CWnd::SubclassDlgItem(unsigned int 1000, CWnd * 0x0012fe34 {CDebugDlg hWnd=0x001d058a}) line 3883 + 12 bytes
  CDebugDlg::OnInitDialog() line 120
  
  可以看出在Attach句柄时出现问题,出问题行的代码为:
  
   ASSERT(m_hWnd == NULL);
  
  这说明我们在子类化时不应该绑定控件,我们删除CDebugDialog::DoDataExchange中的下面一行:
  
   DDX_Control(pDX, IDC_EDIT1, m_edit);
  
  问题就得到解决
  
  总结
  简而言之,call stack是调试中必须掌握的一个技术,但是程序员需要丰富的经验才能很好的掌握和使用它。你不仅仅需要熟知C++语法,还需要对相关的平台、软件设计思路有一定的了解。我的文章只能算一个粗浅的介绍,毕竟我在这方面也不算高手。希望对新进有一定的帮助。
  
  
  调试之编程准备
  
  对于一个程序员而言,学习一种语言和一种算法是非常容易的(不包括那些上学花很多时间玩,上班说学习没时间的人)。但是,任何程序都可能是有瑕疵的,尤其有过团队协作编程经验的人,对这个感触尤为深刻。
  
  
  在我前面的述及调试的文章里,我侧重于VC集成环境中的一些设置信息和调试所需要的一些基本技巧。但是,仅仅知道这些是不够的。一个成功的调试的开端是编程中的准备。
  
  分离错误
  很多程序员喜欢写下面这样的式子:
  
   CLeftView* pView =
   ((CFrameWnd*)AfxGetApp()->m_pMainWnd)->m_wndSplitterWnd.GetPane(0,0);
  
  如果一切顺利,这样的式子当然是没什么问题。但是作为一个程序员,你应该时刻记得任何一个调用在某些特殊的情况下都可能失败,一旦上面某个式子失败,那么整个级联式就会出问题,而你很难弄清楚到底哪儿出错了。这样的式子的结果往往是:省了2分钟编码的时间,多了几星期的调试时间。
  
  对于上面的式子,应该尽可能的把式子分解成独立的函数调用,这样我们可以随时确定是哪个函数调用出问题,进口缩小需要检查的范围。
  
  检查返回值
  检查返回值对于许多编程者来说似乎是一个很麻烦的事情。但是如果你能在每个可能出错的函数调用处都检查返回值,就可以立刻知道出错的函数。
  
  有些人已经意识到检查返回值的重要性,但是要记住,只检查函数是否失败是不够的,我们需要知道函数失败的确切原因。例如下面的代码:
  
  if(connect(sock, (const sockaddr*)&addr,sizeof(addr)) == SOCKET_ERROR)
  {
   AfxMessageBox("connect failed");
  }
  
  尽管这里已经检查了返回值,实际上没有多少帮助。正如很多在vckbase上提问的人一样,大概这时候只能喊“为什么连接失败啊?”。这种情况下,其实只能猜测失败的原因,即使高手,也无法准确说出失败的原因。
  
  增加诊断信息
  在知道错误的情况下,应该尽可能的告诉测试、使用者更多的信息,这样才能了解导致失败的原因。如果程序员能提供如下错误信息,对于诊断错误是非常有帮助的:
  
  出错的文件:我们可以借助宏THIS_FILE和__FILE__。注意THIS_FILE是在cpp文件手工定义的,而__FILE__是编译器定义的。当记录错误的函数定义在.h中时,有时候用THIS_FILE更好,因为他能说明在哪个cpp中调用并导致失败的。
  出错的行:我们可以借助宏__LINE__
  出错的函数:如果设计的好,有以上两项已经足够。当然我们可以直接打印出出错的函数或者表达式,这样在大堆代码中搜索(尤其是不支持go to line的编辑器中)还是很有用的。大家可以参见我的文章http://blog.vckbase.com/arong/archive/2005/11/10/14704.html中的方式进行处理,也许是一个基本的开端。
  出错的原因:出错的原因很多只能由程序自己给出。如果出错只会问别人,那么你永远不可能成为一个合格的程序设计人员。很多函数失败时都会设置errno。我们可以用GetLastError获得错误码,并通过FormatMessage打印出具体错误的文字描述。
  

(4) vc可以调试多线程吗?

答:    可以。

a)首先单击【F10】,开启文件的调试状态

b)单击【Debug】菜单,选择【thread】子菜单

c) 在这里,你可以看到当前所有创建的线程,如果你需要对某一个线程进行调试,那么先把你需要调试的线程设置为Set Focus状态,另外一个thread设置为suspend状态,与此同时resume你需要调试的thread即可。

调试技巧之调用堆栈 - Call stack相关推荐

  1. linux c 用户态 调试追踪函数 调用堆栈 定位段错误

    一般察看函数运行时堆栈的方法是使用GDB(bt命令)之类的外部调试器,但是,有些时候为了分析程序的BUG,(主要针对长时间运行程序的分析),在程序出错时打印出函数的调用堆栈是非常有用的. 在glibc ...

  2. 最大调用堆栈大小超出错误

    我正在使用Direct Web Remoting(DWR)JavaScript库文件,并且仅在Safari(台式机和iPad)中出现错误 它说 超出最大呼叫堆栈大小. 该错误的确切含义是什么,它会完全 ...

  3. 调试小技巧---利用调用堆栈

    2019独角兽企业重金招聘Python工程师标准>>> 调试小技巧---利用调用堆栈 如图:在解决bug的时候,不能确定下一步程序执行到哪,可以利用Xcode中的调用堆栈  来快速查 ...

  4. WinDbg调试dmp(查找问题的异常堆栈时出现的 UnhandledExceptionFilter 调用堆栈跟踪中和其他技巧)

    1.使用windbg打开dump文件,同时设置symbols. 如果之前下载过windows的symbols就直接设置symbols path: (windows和程序的pdb) 例如:C:\Symb ...

  5. 实用调试技巧 Debug Release F10 F11 初始化 越界访问 堆栈 模拟实现库函数

    目录 一.什么是bug 二.调试是什么?有多重要? 1.调试 2.调试的基本步骤 三.Debug和Release 四.windows环境调试介绍 1.调试环境的准备 2.学会快捷键 Ctrl+U 改大 ...

  6. VC++ CallStack调用堆栈应用(调试篇)

    要想学习VC++快,多数人都会选择看视频教程或者学习前辈们的项目源码程序,通常我们是先看视频教程(比如:孙鑫老师的<VC++深入详解>),通过视频教程全面的学习,对VC++有个大致的了解, ...

  7. 使用dbghelp获取调用堆栈--release下的调试方法学

    Author : Kevin Lynx 当软件作为release模式被发布给用户时,当程序崩溃时我们很难去查找原因.常见的手法是输出LOG文件,根据LOG文件分析 程序崩溃时的运行情况.我们可以通过S ...

  8. gdb 命令_gdb实用的调试技巧:启动方式、堆栈信息、单步调试

    对于很多开发者来说,开发过程中难免会遇到各种各样的bug, 所以,每个开发者应该考虑如何快速高效定位问题原因,而gdb是linux上很实用的调试工具,熟练掌握其调试技巧,将有助于提高解决问题的效率,也 ...

  9. .net调试插件sosex的mk命令显示调用堆栈

    !mk 显示当前的调用堆栈 0:029> !mk Thread 29: SP               IP 00:U 00000000053ce1a0 000007fefd2ccacd KE ...

  10. PHP打印调用堆栈信息,用于程序调试

    博客搬家:由于各种原因,我现在的博客将首发于blog.mojijs.com, 可以百度搜索 "姜哥的墨迹技术博客" , 或者 点击这里 本文首发地址 http://blog.moj ...

最新文章

  1. Android常见错误
  2. Linux内核同步:RCU
  3. VB.Net编程实现Web Service的基础
  4. 阿里云地图添加点线面
  5. 发明复制粘贴的那个人去世了
  6. 数据分析软件python 对电脑的要求-数据分析5大软件PK,你最爱哪个?
  7. 区块链预言机(1)初识API网关
  8. 携程编程大赛 (预赛第二场)第一题【剪刀石头布】
  9. 【动态规划】大厅安排 (ssl 1212)
  10. 添加一列_Joom平台CSV文件如何添加产品?CSV文件添加产品流程一览
  11. k8s--configmap
  12. 挂接CreateProcessW实现对进程创建的完全控制
  13. word图片填充之后图片颜色变灰了_一文读懂如何快速实现网页页面变灰?
  14. css-transform-案例-翻转牌效果
  15. 厦门大学c语言模拟考试题,厦门大学《C语言》模拟试卷(10级).doc
  16. 在自动驾驶技术上,一向自信满满的马斯克也承认了特斯拉的不足
  17. 陈天出席华盛顿大学春季招聘会 | ArcBlock 动态
  18. 华为云迁移工具推荐最佳实践:Hyper-V虚拟化迁移到华为云
  19. 异常测试之Socket网络异常
  20. MaximalRectangle

热门文章

  1. 树莓派安装centos操作系统
  2. pytorch关系抽取框架OpenNRE源码解读与实践:PCNN ATT
  3. U分布、T分布、z分位数
  4. GIMP 2.10.12图片背景透明处理
  5. 计算机公共基础课网络教学平台,“计算机基础”教学中网络教学平台应用研究.doc...
  6. c语言程序100例pdf,经典c程序100例源代码(全).pdf
  7. mysql中的rman备份与恢复_RMAN备份与恢复总汇
  8. 计算机端口详细介绍(整理版)
  9. 软件质量管理-考试复习总结
  10. div+js写弹出框