利用 VC++ 6.0 调试
编写程序,定义 n = 123,打印 Hello world!
#include int main(){ int n = 123; printf("Hello world!\r\n"); return 0;}
按 F7 进行编译,F9 下断点(运行到短点停下来),F5 运行, shift + F5 终止调试。
按 F10 进行单步跳过调试。进入调式模式。黄色箭头的指向,表示程序流程的位置,指的是即将执行但尚未执行的代码行。这时左花括号还未执行 { 。
此时在观察框,输入 n 和 &n,提示:CXX0069:Error:variable needs stack Frame(错误:变量需要堆框架)。
再按 F10 ,程序执行左花括号 { 完毕 ,&n 是 0x19ff2c ,n 的值还没有初始化
再按 F10 ,int n = 123 执行完毕,此时 n 的值是 0x0000007b,也就是 10 进制的 123
此时,黄色箭头指向了 printf() 函数
这时有两种选择,一种是 F10 单步跳过调试,直接到 return 0,另一种是 F11 单步进入调试,进入到 printf 函数的内部。这里按 F11 进入 printf 函数的内部 。进入 printf 函数的内部后,继续 F11 进入了 _ftbuf 函数的内部,继续 F11 进入了 _flush 函数的内部,继续 F11 进入了 _write 函数内部。如果不记得来时的路,打开栈窗口,可以看到此时在 _write 函数,是从 _flush 函数进来的,依次类推。
跳回上一个函数,按 shift + F11
跳回多层函数。通过下断点运行的方式,跳回 main 函数的 printf 函数位置。双击 main 函数,在 printf 函数的位置按 F9 下断点
按 F5 执行,就会跳到 printf 函数的位置
分支语句 if
编写一个多分支程序,判断 123 除以 5 的余数是多少
#include int main(){ int n = 123; if(n % 5 == 0) { printf("n % 5 == 0"); } else if(n % 5 == 1) { printf("n % 5 == 1"); } else if(n % 5 == 2) { printf("n % 5 == 2"); } else if(n % 5 == 3) { printf("n % 5 == 3"); } else { printf("n % 5 == 4"); } return 0;}
按 F10 进入单步调试模式
此时在观察框输入 n 和 &n 以及 n % 5 ,提示:CXX0069:Error:variable needs stack Frame(错误:变量需要堆框架)。
再按 F10 执行完左花括号 {
此时 n 的值是 0xcccccccc,&n 的值是 0x0019ff2c,n % 5 的值是 0x00000000
再按 F10 执行完 int n = 123
此时 n 的值是 0x0000007b ,&n 的值是 0x0019ff2c ,n % 3 的值是 0x00000003
再按 F10 ,因为 if(n % 5 == 0) 不成立,直接跳到 else if (n % 5 == 1)
再按 F10 ,因为 else if(n % 5 == 1) 不成立,直接跳到 else if (n % 5 == 2)
再按 F10 ,因为 else if(n % 5 == 2) 不成立,直接跳到 else if (n % 5 == 3)
再按 F10 ,因为 else if(n % 5 == 3) 成立,进入到分支条件内部 printf("n % 5 == 3")
再按 F10,跳出 else if(n % 5 == 3) 的分支,进入到 else
再按 F10 ,因为 else 不成立,进入到 reutrn 0
通过上面的调试过程,可以看出。虽然很早程序就知道了 123 % 5 == 3,但是还是按顺序执行。从时间成本和系统执行次数来看,放在前面的 if 所需的代价是很小的,但是越往后面的 else if ,else 所需代价就越来越大。
分支语句 switch
调试 switch
编写一个 switch 多分支程序,判断 123 除以 5 的余数是多少,其中 case 2,case 3 不加 break
#include int main(){ int n = 123; switch(n % 5) { case 0: printf("case 0\r\n"); break; default: printf("default\r\n"); case 1: printf("case 1\r\n"); break; case 2: printf("case 2\r\n"); case 3: printf("case 3\r\n"); case 4: printf("case 4\r\n"); break; } return 0;}
按 F10 进入单步调试
此时在观察框输入 n 和 &n 以及 n % 5 ,提示:CXX0069:Error:variable needs stack Frame(错误:变量需要堆框架)。
再按 F10 ,左花括号执行完毕
此时 n 的值是 0xcccccccc,&n 的值是 0x0019ff2c,n % 5 的值是 0x00000000
再按 F10,int n = 123 执行完毕
此时 n 的值是 0x0000007b ,&n 的值是 0x0019ff2c ,n%5 的值是 0x00000003
再按 F10 ,直接跳入 case 3中,即将执行 printf("case 3\r\n")
因为 case3 没有 break ,再按 F10 ,直接跳入 case 4中,即将执行 printf("case 4\r\n")
switch 机制
通过上面的调试过程可以看到,当算出 n % 5 == 3 时,并没有按顺序执行 case 0 ,default,case 1,case 2,而是直接跳到了 case 3 处,然后执行了 case 4。所有的 case 代价都是均等的,没有在前面就优先的机制。
接下来说一下,switch 较 case 做了哪些优化。这里只说优化的其中一点。每个 case 都有自己的地址,不一定是有序的,因为 case 的顺序是由程序员来决定的。如果 case 的数据顺序连续,switch 的机制中会做个表,这个表就是数组,下标按 0 1 2 3 顺序排 。数组 0 记录了 case 0 的地址,数组 1 记录了 case 1 的地址,数组 2 记录了 case 2 的地址,以此类推。如果 case 的数据不连续,断的地方用 default 代替。当 switch 算出值之后,求出值是什么,就在数组中取这个值的地址,然后流程转到这个地址去。
明白了 switch 机制后,按 F10 调式,在 switch 左括号 { 处按 alt + 8 进入反汇编模式。找到最后一串代码,最后一串数字,是 40D792h
在地址窗口输入 40D792h,里面的值是 00 40 D7 2D ,00 40 D7 49 ,00 40 D7 58 ,00 40 D7 65 ,00 40 D7 72 。一共 5 个,后面就没有了。
case 0 的地址是 00 40 D7 2D
case 1 的地址是 00 40 D7 49 , case 2 的地址是 00 40 D7 58 ,case 3 的地址是 00 40 D7 65 ,case 4 的地址是 00 40 D7 72
将源程序 case 顺序打乱
将 case 打乱,再写一个程序
#include int main(){ int n = 123; switch(n % 5) { case 4: printf("case 4\r\n"); break; case 0: printf("case 0\r\n"); break; default: printf("default\r\n"); case 3: printf("case 3\r\n"); case 2: printf("case 2\r\n"); case 1: printf("case 1\r\n"); break; } return 0;}
同样方式,在 switch 左括号 { 处按 alt + 8 进入反汇编模式。找到最后一串代码,最后一串数字,仍然是 40D792h
在地址窗口输入 40D792h,里面的值是 00 40 D7 3C ,00 40 D7 72 ,00 40 D7 65 ,00 40 D7 58 ,00 40 D7 2D 。一共 5 个,后面就没有了。
case 4 的地址是 00 40 D7 2D ,case 0 的地址是 00 40 D7 3C
case 3 的地址是 00 40 D7 58 ,case 2 的地址是 00 40 D7 65 ,case 1 的地址是 00 40 D7 72
将其中一个 case 删掉
将上一个程序的 case3 删掉
#include int main(){ int n = 123; switch(n % 5) { case 4: printf("case 4\r\n"); break; case 0: printf("case 0\r\n"); break; default: printf("default\r\n"); case 2: printf("case 2\r\n"); case 1: printf("case 1\r\n"); break; } return 0;}
同样方式,在 switch 左括号 { 处按 alt + 8 进入反汇编模式。找到最后一串代码,最后一串数字,是 40D785h
在地址窗口输入 40D785h,里面的值是 00 40 D7 3C ,00 40 D7 65 ,00 40 D7 58 ,00 40 D7 48 ,00 40 D7 2D 。一共 5 个,后面就没有了。
case4 的地址是 00 40 D7 2D ,case0 的地址是 00 40 D7 3C ,default 的地址(占用了 case3 的地址)是 00 40 D7 48,case2 的地址是 00 40 D7 58 ,case1 的地址是 00 40 D7 65
case 的数据过大
再写一个程序,最 case 从 case 100 开始
#include int main(){ int n = 123; switch(n % 5) { case 100: printf("case 4\r\n"); break; case 101: printf("case 0\r\n"); break; default: printf("default\r\n"); case 104: printf("case 2\r\n"); case 105: printf("case 1\r\n"); break; } return 0;}
同样方式,在 switch 左括号 { 处按 alt + 8 进入反汇编模式。找到最后一串代码,最后一串数字,是 40D785h 。
在地址窗口输入 40D785h,里面的值是 00 40 D7 36 ,00 40 D7 45 ,00 40 D7 54 ,00 40 D7 54 ,00 40 D7 61 ,00 40 D7 6E 。一共 6 个,后面就没有了。
如果 case 数据过大,每个 case 数据,减去 case 中最小的数据。这里最小的数据是 100 ,所以都减去 100。仍然是数组的 0 1 2 3 4 5 6,因为没有 case 102 103 ,所以用 default 的地址代替。
所以 case100 的地址是 00 40 D7 36 ,case101 的地址是 00 40 D7 45 ,default 的地址(占用了 case102 103 的地址)是 00 40 D7 54,case104 的地址是 00 40 D7 61 ,case105 的地址是 00 40 D7 6E