程序调试技巧之调用堆栈
linux C用户态调试追踪函数调用堆栈以及定位段错误

linux C用户态调试追踪函数调用堆栈以及定位段错误一般察看函数运行时堆栈的方法是使用GDB(bt命令)之类的外部调试器,但是,有些时候为了分析程序的BUG,(主要针对长时间运行程序的分析),在程序出错时打印出函数的调用堆栈是非常有用的。
在glibc头文件"execinfo.h"中声明了三个函数用于获取当前线程的函数调用堆栈。
int backtrace(void **buffer,int size)该函数用于获取当前线程的调用堆栈,获取的信息将会被存放在buffer中,它是一个指针列表。
参数size 用来指定buffer中可以保存多少个void* 元素。
函数返回值是实际获取的指针个数,最大不超过size大小在buffer中的指针实际是从堆栈中获取的返回地址,每一个堆栈框架有一个返回地址注意:某些编译器的优化选项对获取正确的调用堆栈有干扰,另外内联函数没有堆栈框架;删除框架指针也会导致无法正确解析堆栈内容char ** backtrace_symbols (void *const *buffer, int size)backtrace_symbols将从backtrace函数获取的信息转化为一个字符串数组. 参数buffer应该是从backtrace函数获取的指针数组,size是该数组中的元素个数(backtrace的返回值)函数返回值是一个指向字符串数组的指针,它的大小同buffer相同.每个字符串包含了一个相对于buffer中对应元素的可打印信息.它包括函数名,函数的偏移地址,和实际的返回地址现在,只有使用ELF二进制格式的程序才能获取函数名称和偏移地址.在其他系统,只有16进制的返回地址能被获取.另外,你可能需要传递相应的符号给链接器,以能支持函数名功能(比如,在使用GNU ld链接器的系统中,你需要传递(-rdynamic),-rdynamic可用来通知链接器将所有符号添加到动态符号表中,如果你的链接器支持-rdynamic的话,建议将其加上!) 该函数的返回值是通过malloc函数申请的空间,因此调用者必须使用free函数来释放指针. 注意:如果不能为字符串获取足够的空间函数的返回值将会为NULLvoid backtrace_symbols_fd (void *const *buffer, int size, int fd)。
arm函数调用中的堆栈变化

arm函数调用中的堆栈变化在计算机的运行过程中,函数调用是一种常见的操作。
当程序调用一个函数时,需要先将当前的运行状态(例如当前指令的地址、堆栈指针等)保存在堆栈中,然后跳转到函数中执行。
函数执行完毕后,再从堆栈中恢复之前的运行状态,继续执行原来的程序。
这个过程中,堆栈扮演了一个非常重要的角色。
本文将介绍在ARM架构中,函数调用时堆栈的变化。
1. 堆栈的基本概念在程序中,有一片内存区域被用来存放函数的局部变量和一些临时变量,称为堆栈。
堆栈是一个先进后出的数据结构,即最后存入的数据最先弹出。
当程序调用一个函数时,会在堆栈中分配一段空间来存放函数的参数、局部变量、返回地址等信息。
当函数返回时,这些信息会从堆栈中弹出,恢复程序之前的状态。
在ARM架构中,堆栈的地址是4字节对齐的,即堆栈指针(SP)的值必须是4的倍数。
这是因为ARM指令集中的大多数指令都是以4字节为单位的,如果SP不是4字节对齐的,那么执行指令时会出错。
2. 函数调用时堆栈的变化当程序调用一个函数时,堆栈的变化可以分为以下几个步骤:(1)保存寄存器在ARM架构中,函数调用过程中一些重要的状态信息通常保存在寄存器中。
为了不影响原程序的运行,需要在堆栈中保存这些寄存器的值。
这些寄存器包括:R0~R3(函数参数)、R14(LR,返回地址)、R13(SP,堆栈指针)、R11(FP,帧指针)等。
在进入函数之前,需要将这些寄存器的值压入堆栈中。
具体操作为:``` PUSH {R0-R3, R11, LR} ```以上指令将R0~R3、R11、LR的值压入堆栈中。
其中,LR保存的是返回地址,R11保存的是帧指针(Frame Pointer,FP)。
FP是一个指针,指向当前函数的栈帧,用于访问局部变量。
堆栈指针SP也需要被保存,但不是在这里保存,而是在进入函数之前保存。
(2)分配空间在堆栈中为当前函数分配空间,用于存放参数、局部变量和其它临时变量。
分配的空间的大小由当前函数所需的局部变量和参数决定。
调用堆栈

首先介绍一下什么叫调用堆栈:假设我们有几个函数,分别是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 hereASSERT(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 88CCmdTarget::OnCmdMsg(unsigned int 1, int 0, void * 0x00000000, AFX_CMDHANDLERINFO * 0x00000000) line 302 + 39 bytesCDialog::OnCmdMsg(unsigned int 1, int 0, void * 0x00000000, AFX_CMDHANDLERINFO * 0x00000000) line 97 + 24 bytesCWnd::OnCommand(unsigned int 1, long 656988) line 2088CWnd::OnWndMsg(unsigned int 273, unsigned int 1, long 656988, long * 0x0012f83c) line 1597 + 28 bytesCWnd::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 bytesAfxWndProc(HWND__ * 0x001204b0, unsigned int 273, unsigned int 1, long 656988) line 368AfxWndProcBase(HWND__ * 0x001204b0, unsigned int 273, unsigned int 1, long 656988) line 220 + 21 bytesUSER32! 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 182CWnd::PreTranslateInput(tagMSG * 0x004167d8 {msg=0x00000202 wp=0x00000000 lp=0x000f001c}) line 3424CDialog::PreTranslateMessage(tagMSG * 0x004167d8 {msg=0x00000202 wp=0x00000000 lp=0x000f001c}) line 92CWnd::WalkPreTranslateTree(HWND__ * 0x001204b0, tagMSG * 0x004167d8 {msg=0x00000202 wp=0x00000000 lp=0x000f001c}) line 2667 + 18 bytesCWinThread::PreTranslateMessage(tagMSG * 0x004167d8 {msg=0x00000202 wp=0x00000000 lp=0x000f001c}) line 665 + 18 bytesCWinThread::PumpMessage() line 841 + 30 bytesCWnd::RunModalLoop(unsigned long 4) line 3478 + 19 bytesCDialog::DoModal() line 536 + 12 bytesCDebugApp::InitInstance() line 59 + 8 bytesAfxWinMain(HINSTANCE__ * 0x00400000, HINSTANCE__ * 0x00000000, char * 0x00141f00, int 1) line 39 + 11 bytesWinMain(HINSTANCE__ * 0x00400000, HINSTANCE__ * 0x00000000, char * 0x00141f00, int 1) line 30WinMainCRTStartup() line 330 + 54 bytesKERNEL32! 7c816d4f()这里,CDebugDialog::OnOK作为整个调用链中最后被调用的函数出现在call stack的最上方,而内核中程序的启动函数Kernel32! 7c816d4f()则作为栈底出现在最下方。
调用函数的压堆栈方式

调用函数的压堆栈方式
在计算机编程中,当一个函数被调用时,会发生压栈操作。
这
是因为计算机需要保存当前函数的执行状态,以便在函数执行完毕
后能够回到调用该函数的地方继续执行。
下面我将从多个角度来解
释函数的压栈方式。
1. 参数传递,在调用函数时,参数会被压入栈中。
这样函数就
可以在栈中找到这些参数并使用它们。
2. 返回地址,在调用函数时,调用方的返回地址会被压入栈中。
这样函数执行完毕后可以通过返回地址回到调用方。
3. 保存旧的栈帧指针,在函数调用时,当前函数的栈帧指针会
被压入栈中,以便在函数执行完毕后能够回到调用方的栈帧。
4. 保存局部变量,在函数调用时,当前函数的局部变量会被压
入栈中,以便在函数执行期间可以使用这些局部变量。
5. 保存寄存器状态,在函数调用时,一些寄存器的状态会被保
存到栈中,以便函数执行期间可以使用这些寄存器。
总的来说,函数的压栈方式是为了保存当前函数的执行状态,以便在函数执行完毕后能够回到调用方继续执行。
这种方式是计算机实现函数调用和返回的基础,也是程序执行的重要机制之一。
希望这些解释能够帮助你理解函数的压栈方式。
汇编语言堆栈指令

汇编语言堆栈指令汇编语言是一种底层的计算机语言,它直接操作计算机的硬件。
在汇编语言中,堆栈(Stack)是一种重要的数据结构,用于存储程序执行时的临时数据和返回地址等信息。
堆栈指令用于操作堆栈,包括入栈、出栈、压栈和弹栈等操作。
本文将从堆栈指令的角度介绍汇编语言的相关知识。
一、入栈指令入栈指令用于将数据压入堆栈,常用的入栈指令有PUSH和PUSHA。
PUSH指令可以将立即数或寄存器中的值压入堆栈,而PUSHA指令可以将通用寄存器中的值一次性压入堆栈。
入栈指令的作用是保存临时数据,以便后续的操作使用。
二、出栈指令出栈指令用于将数据从堆栈中弹出,常用的出栈指令有POP和POPA。
POP指令可以将堆栈顶部的数据弹出并存入指定的寄存器,而POPA 指令可以一次性将堆栈中的数据弹出并存入通用寄存器。
出栈指令的作用是恢复之前保存的数据,以便继续执行程序。
三、堆栈指针堆栈指针(Stack Pointer)是一个特殊的寄存器,用于指示当前堆栈的顶部位置。
在x86架构中,堆栈指针通常用ESP表示。
入栈和出栈指令会自动更新堆栈指针的值,以保证数据正确地压入和弹出堆栈。
四、压栈和弹栈压栈和弹栈是堆栈操作中的两个重要概念。
压栈(Push)指的是将数据从数据段移动到堆栈段的过程,堆栈指针会自动减小。
弹栈(Pop)指的是将数据从堆栈段移动到数据段的过程,堆栈指针会自动增加。
压栈和弹栈是堆栈操作的基本操作,用于实现数据的存储和读取。
五、堆栈的应用堆栈在汇编语言中有着广泛的应用,它可以用于实现函数的调用和返回、保存寄存器的状态、传递参数和局部变量等。
函数的调用和返回是汇编语言程序中常见的操作,它们依赖于堆栈来传递参数和保存返回地址。
当一个函数被调用时,参数会被压入堆栈,函数执行完毕后,返回地址会从堆栈中弹出,程序继续执行返回地址指向的位置。
堆栈还可以用于保存寄存器的状态。
在汇编语言中,为了保护现场,程序在执行前会将当前寄存器的值保存到堆栈中,执行完毕后再将堆栈中的值恢复到寄存器中。
Visual Studio 2015中的常用调试技巧分享

Visual Studio 2015中的常用调试技巧分享.NET 技术交流群:337901356 欢迎您的加入!为什么要学习调试?调试(Debug)是作为一个程序员必须要学会的东西,学会调试可以极大的提高开发效率,排错时间,很多人不喜欢调试,但我认为这是一个很不可取的选择,调试的时候能让我们看到程序的执行顺序、步骤以及过程等,调试的时候可以让我们监视代码中各个变量的情况,调试让我们可以让我们快速的找出错误的根源。
可见调试是至关重要的。
要学习好怎么调试,那么必须去了解VS 这个IDE中的各种调试技巧,下面我就讲讲我所经常在调试程序中所用到的技巧。
调试技巧介绍1、监视窗口(Ctrl+D,Ctrl+W 快捷键开启)我们在调试程序的过程中,可以通过此窗口动态查看各个变量的值,以及各个函数的调用的返回结果。
在监视窗口中,我们还可以手动更改某个变量的值,这个有时候很有用,特别是有时候程序执行到指定语句的时候,发现某个值是错误的,但是我们又想用一个正确值测试代码时,此时可以通过监视窗口直接更改变量的值,而不需要重新启动调试。
快速监视:选中某个变量后者表达式,然后通过按下快捷键Ctrl+D,Ctrl+Q 开启。
备注:只能在调试情况下才能开启此窗口。
2、调用堆栈(Ctrl+D,Ctrl+C)通过该窗口,我们可以看到函数的一级一级的调用过程,我们就可以知道,该方法是来自于上面的哪一个步骤发起的调用。
、可以通过点击调试->窗口->调用堆栈来打开调用堆栈窗口。
如下图:备注:只能在调试情况下在可以开启此窗口。
3、拖动调试光标的技巧。
Visual Studio 在调试的情况下可以拖动左侧的黄色箭头进行上下拖动,那么这个有什么作用呢,有时候我们可能想实用F11跟到某个方法里面进行调用过程的查看,结果一个不小心发现手误按下了F10,此时代码执行到了方法调用的下一句,那么我们此时就可以点击左侧的黄色箭头,并按住鼠标左键,往上一拖,这个时候,就又可以执行刚才的方法调用的那句代码了,如果往下拖,那么可以跳过一些语句代码的执行。
double函数调用堆栈过程

double函数调用堆栈过程一、概述函数调用堆栈是编程中一个重要的概念,它用于存储函数调用的信息。
当一个函数被调用时,其参数、局部变量和返回地址等信息会被压入堆栈;当函数执行完毕返回时,这些信息会从堆栈中弹出。
double函数调用同样遵循这样的过程,它涉及到两个函数间的相互调用和参数传递。
1. 函数调用:当执行到double函数调用时,首先将当前函数的返回地址压入堆栈的顶部。
这是为了在后续返回调用函数时能够正确返回调用函数的返回值。
2. 参数传递:接下来,double函数会将需要传递给它的参数压入堆栈。
这些参数通常是从调用double函数的函数中传递过来的。
3. 局部变量:在堆栈中,double函数还会保存其自身的局部变量。
这些变量在double函数执行期间有效,当函数执行完毕后,这些变量会被清除。
4. 执行double函数:当double函数开始执行时,它会使用堆栈中的参数和局部变量进行运算或处理。
5. 返回调用函数:当double函数执行完毕后,它会将返回地址从堆栈中弹出,并跳转到这个地址处继续执行后续代码。
6. 清理堆栈:最后,当double函数返回后,其占用的堆栈空间会被释放,以便于下一个函数的调用。
三、注意事项1. 确保堆栈空间足够:在调用double函数之前,需要确保堆栈空间足够,以存储返回地址、参数和局部变量等信息。
2. 避免堆栈溢出:在处理大量数据或递归调用时,要特别注意堆栈溢出的问题。
可以使用适当的数据结构或算法来避免过大的数据占用过多的堆栈空间。
3. 正确处理返回值:在调用double函数时,需要确保返回地址能够正确返回调用函数的返回值。
如果返回地址被覆盖或错误处理,可能会导致程序错误或异常。
4. 合理使用局部变量:在double函数中使用的局部变量应当根据实际需求进行合理设置和分配。
过多的局部变量可能导致堆栈溢出或影响程序的性能。
5. 调试和错误处理:在编写代码时,需要对可能出现的错误和异常进行充分考虑和测试。
eclipse堆栈调用流程

Eclipse是一个集成开发环境(IDE),它允许开发者编写、调试和运行各种类型的软件,包括Java、C++、Python等。
Eclipse使用堆栈来跟踪方法的调用和返回,这被称为堆栈帧。
下面是一个简单的Java 程序的堆栈跟踪示例:假设我们有一个简单的Java程序,其中有一个主类(Main)和一个辅助类(Helper)。
```javapublic class Main {public static void main(String[] args) {Helper helper = new Helper();helper.doSomething();}}public class Helper {public void doSomething() {System.out.println("Doing something...");doSomethingElse();}public void doSomethingElse() {System.out.println("Doing something else...");}}```当我们在Eclipse中运行这个程序时,Eclipse会显示一个堆栈跟踪窗口,显示方法的调用和返回。
堆栈跟踪窗口通常显示当前方法、调用该方法的父方法、再上一级的父方法等,直到主方法(main方法)。
堆栈跟踪的步骤如下:1. 程序开始执行。
主线程启动,并执行主方法(main)。
这会创建一个新的堆栈帧,并将它放在堆栈顶部。
堆栈帧中包含方法的局部变量、操作数栈、动态链接和方法出口信息。
2. 在主方法中,创建一个Helper对象,并将其引用存储在变量helper中。
这不会导致堆栈帧的变化,因为对象是在堆上分配的,而不是在堆栈上。
3. 调用helper对象的doSomething方法。
这会导致当前线程从主方法中跳转到doSomething方法,并将一个新的堆栈帧放在堆栈顶部。
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
调试技巧之调用堆栈在计算机科学中,Callstack是指存放某个程序的正在运行的函数的信息的栈。
Call stack由stack frames组成,每个stack frame对应于一个未完成运行的函数。
在当今流行的计算机体系架构中,大部分计算机的参数传递,局部变量的分配和释放都是通过操纵程序栈来实现的。
栈用来传递函数参数,存储返回值信息,保存寄存器以供恢复调用前处理机状态。
每次调用一个函数,都要为该次调用的函数实例分配栈空间。
为单个函数分配的那部分栈空间就叫做stack frame,也就是说,stack frame这个说法主要是为了描述函数调用关系的。
Stackframe组织方式的重要性和作用体现在两个方面:第一,它使调用者和被调用者达成某种约定。
这个约定定义了函数调用时函数参数的传递方式,函数返回值的返回方式,寄存器如何在调用者和被调用者之间进行共享;第二,它定义了被调用者如何使用它自己的stack frame来完成局部变量的存储和使用。
简单介绍调试是程序开发者必备技巧。
如果不会调试,自己写的程序一旦出问题,往往无从下手。
本人总结10年使用VC经验,对调试技巧做一个粗浅的介绍。
希望对大家有所帮助。
今天简单的介绍介绍调用堆栈。
调用堆栈在我的专栏的文章VC调试入门提了一下,但是没有详细介绍。
首先介绍一下什么叫调用堆栈:假设我们有几个函数,分别是function1,function2,function3,funtion4,且function1调用function2,function2调用function3,function3调用function4。
在function4运行过程中,我们可以从线程当前堆栈中了解到调用他的那几个函数分别是谁。
把函数的顺序关系看,function4、function3、function2、function1呈现出一种“堆栈”的特征,最后被调用的函数出现在最上方。
因此称呼这种关系为调用堆栈(callstack)。
当故障发生时,如果程序被中断,我们基本上只可以看到最后出错的函数。
利用call stack,我们可以知道当出错函数被谁调用的时候出错。
这样一层层的看上去,有时可以猜测出错误的原因。
常见的这种中断时ASSERT宏导致的中断。
在程序被中断时,debug工具条的右侧倒数第二个按钮一般是callstack按钮,这个按钮被按下后,你就可以看到当前的调用堆栈。
实例一:介绍我们首先演示一下调用堆栈。
首先我们创建一个名为Debug的对话框工程。
工程创建好以后,双击OK按钮创建消息映射函数,并添加如下代码:void CDebugDlg::OnOK(){//TODO:Add extravalidation hereASSERT(FALSE);}我们按F5开始调试程序。
程序运行后,点击OK按钮,程序就会被中断。
这时查看call stack窗口,就会发现内容如下:CDebugDlg::OnOK()line176+34bytes_AfxDispatchCmdMsg(CCmdTarget*0x0012fe74{CDebugDlg},unsigned int1,int0, void(void)*0x5f402a00`vcall'(void),void*0x00000000,unsigned int12, AFX_CMDHANDLERINFO*0x00000000)line88CCmdTarget::OnCmdMsg(unsigned int1,int0,void*0x00000000, AFX_CMDHANDLERINFO*0x00000000)line302+39bytesCDialog::OnCmdMsg(unsignedint1,int0,void*0x00000000,AFX_CMDHANDLERINFO *0x00000000)line97+24bytesCWnd::OnCommand(unsignedint1,long656988)line2088CWnd::OnWndMsg(unsignedint273,unsigned int1,long656988,long*0x0012f83c) line1597+28bytesCWnd::WindowProc(unsignedint273,unsigned int1,long656988)line1585+30 bytesAfxCallWndProc(CWnd*0x0012fe74{CDebugDlg hWnd=???},HWND__*0x001204b0, unsigned int273,unsigned int1,long656988)line215+26bytes AfxWndProc(HWND__*0x001204b0,unsigned int273,unsigned int1,long656988) line368AfxWndProcBase(HWND__*0x001204b0,unsigned int273,unsigned int1,long656988)line220+21bytesUSER32!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=0x00000202wp=0x00000000 lp=0x000f001c})line182CWnd::PreTranslateInput(tagMSG*0x004167d8{msg=0x00000202wp=0x00000000 lp=0x000f001c})line3424CDialog::PreTranslateMessage(tagMSG*0x004167d8{msg=0x00000202 wp=0x00000000lp=0x000f001c})line92CWnd::WalkPreTranslateTree(HWND__*0x001204b0,tagMSG*0x004167d8 {msg=0x00000202wp=0x00000000lp=0x000f001c})line2667+18bytes CWinThread::PreTranslateMessage(tagMSG*0x004167d8{msg=0x00000202 wp=0x00000000lp=0x000f001c})line665+18bytesCWinThread::PumpMessage()line841+30bytesCWnd::RunModalLoop(unsignedlong4)line3478+19bytesCDialog::DoModal()line536+12bytesCDebugApp::InitInstance()line59+8bytesAfxWinMain(HINSTANCE__*0x00400000,HINSTANCE__*0x00000000,char* 0x00141f00,int1)line39+11bytesWinMain(HINSTANCE__*0x00400000,HINSTANCE__*0x00000000,char*0x00141f00, int1)line30WinMainCRTStartup()line330+54bytesKERNEL32!7c816d4f()这里,CDebugDialog::OnOK作为整个调用链中最后被调用的函数出现在callstack的最上方,而内核中程序的启动函数Kernel32!7c816d4f()则作为栈底出现在最下方。
实例二:学习处理方法微软提供了MDI/SDI模型提供文档处理的建议结构。
有些时候,大家希望控制某个环节。
例如,我们希望弹出自己的打开文件对话框,但是并不想自己实现整个文档的打开过程,而更愿意MFC完成其他部分的工作。
可是,我们并不清楚MFC是怎么处理文档的,也不清楚如何插入自定义代码。
幸运的是,我们知道当一个文档被打开以后,系统会调用CDocument派生类的Serialize 函数,我们可以利用这一点来跟踪MFC的处理过程。
我们首先创建一个缺省的SDI工程Test1,并在CTest1Doc::Serialize函数的开头增加一个断点,运行程序,并打开一个文件。
这时,我们可以看到调用堆栈是(我只截取了感兴趣的一段):CTest1Doc::Serialize(CArchive&{...})line66CDocument::OnOpenDocument(constchar*0x0012f54c)line714CSingleDocTemplate::OpenDocumentFile(constchar*0x0012f54c,int1)line168 +15bytesCDocManager::OpenDocumentFile(constchar*0x0042241c)line953CWinApp::OpenDocumentFile(constchar*0x0042241c)line93CDocManager::OnFileOpen()line841CWinApp::OnFileOpen()line37_AfxDispatchCmdMsg(CCmdTarget*0x004177f0class CTest1App theApp,unsigned int 57601,int0,void(void)*0x00402898CWinApp::OnFileOpen,void*0x00000000, unsigned int12,AFX_CMDHANDLERINFO*0x00000000)line88CCmdTarget::OnCmdMsg(unsignedint57601,int0,void*0x00000000, AFX_CMDHANDLERINFO*0x00000000)line302+39bytesCFrameWnd::OnCmdMsg(unsignedint57601,int0,void*0x00000000, AFX_CMDHANDLERINFO*0x00000000)line899+33bytesCWnd::OnCommand(unsignedint57601,long132158)line2088CFrameWnd::OnCommand(unsignedint57601,long132158)line317从上面的调用堆栈看,这个过程由一个WM_COMMAND消息触发(因为我们用菜单打开文件),由CWinApp::OnFileOpen最先开始实际处理过程,这个函数调用CDocManager::OnFileOpen打开文档。