文章目录
0、总结
1、bug和调试(debug)
bug:一般是指在电脑系统或程序中,隐藏着的一些未被发现的缺陷或问题,简称程序漏洞。
debug:找问题的过程称之为调试,英文叫debug,意思是消灭bug。
调试一个程序,首先是承认出现了问题,然后通过各种手段去定位问题的位置。可能是逐过程的调试,也可能是隔离和屏蔽代码的方式,找到问题所在的位置,然后确定错误产生的原因,再修复代码,重新测试。
2、Debug和Release
在VS上编写代码的时候,就能看到有Debug
和Release
两个选项,分别是什么意思呢?
Debug:称为调试版本,它包含调试信息,不进行任何优化,便于程序员调试。编写代码时,若需频繁调试,则设置为Debug
模式,编译后产生的是可直接调试的Debug
版本可执行程序。
Release:称为发布版本,它经过各种优化,以达到代码大小和运行速度的最佳状态,便于用户使用。程序员完成代码编写并经过充分测试,确保程序质量符合交付标准后,会设置为Release
模式进行编译,产生用户使用的Release
版本可执行程序,该版本不包含调试信息。
对比可以看到从同一段代码,编译生成的可执行文件的大小,release版本明显要小,而debug版本明显要大。
3、VS调试快捷键
程序员如何调试代码?
1、首先是环境准备,把VS上设置为Debug
,如图:
2、其次,掌握调试中最常使用的几个快捷键:
- F9:用于创建或取消断点。断点允许在程序的任意位置暂停执行,便于使用F10、F11等快捷键深入观察代码的执行细节。
- 条件断点:仅当满足特定条件时,才会触发断点。
- F5:启动调试,通常与F9配合使用,用于直接跳转到下一个断点处。
- F10:逐过程执行,用于处理一个完整的过程,如函数调用或单条语句。
- F11:逐语句执行,每次执行一条语句,能深入函数内部观察执行逻辑的细节,想探究细节需要使用F11,用F10则跳过。
4、监视和内存观察
在调试过程中,若要观察代码执行时变量的值,可以使用监视和内存窗口来进行观察,但观察的前提条件是已经开始调试。
例1:
#include <stdio.h>
int main()
{
int arr[10] = { 0 };
int num = 100;
char c = 'w';
int i = 0;
for (i = 0; i < 10; i++)
{
arr[i] = i;
}
return 0;
}
4.1、监视
开始调试后,在菜单栏中【调试】->【窗口】->【监视】,打开任意一个监视窗口,输入想要观察的对象即可。
打开监视窗口,如图:
在监视窗口中观察对象:
4.2、内存
如果监视窗口看的不够仔细,也可以观察变量在内存中的存储情况,还是在【调试】->【窗口】->【内存】中。
打开内存窗口:
在内存窗口中观察数据:
打开内存窗口后,在地址栏输入:arr
,就能观察到该地址处的数据。如图:
除此之外,在调试的窗口中还有:自动窗口、局部变量、反汇编、寄存器等窗口。
5、调试举例
例1:在VS2022、X86、Debug的环境下,编译器不做任何优化的话,下面代码执行的结果是多少?
#include <stdio.h>
int main()
{
int i = 0;
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
for (i = 0; i <= 12; i++)
{
arr[i] = 0;
printf("hello\n");
}
return 0;
}
输出:
死循环
调试观察,上面程序的内存布局如图:
- 1、栈区内存的使用习惯是从高地址向低地址使用的,所以变量
i
的地址是较大的。arr
数组的地址整体是小于i
的地址。但需注意,栈区的具体使用习惯依赖于编译器的实现,如在VS的X64模式或Release版本中,这一习惯可能相反。 - 2、数组在内存中的存放是:随着下标的增长,地址是由低到高变化的。
- 3、按照图中的内存布局,随着数组下标的增长,往后越界就有可能覆盖到
i
,这样就可能造成死循环。
为什么i
和arr
数组之间恰好空出来2个整型的空间呢?
这个是巧合,不同的编译器可能会在中间空出不同大小的空间,因为代码中变量的内存和地址分配是由编译器决定的,所以不同编译器之间存在差异。因此,这个问题与环境相关。
从这个例子中,能体会到调试的重要性,只有调试才能观察到程序内部执行的细节。就像医生通过给病人进行B超、CT等检查来找出病因一样。
例2:在数组传参,调试进入函数,如何在监视窗口观察数组的内容?
#include <stdio.h>
void test1(int d[])
{
}
int main()
{
int data[10] = { 1,3,5,7,9 };
test1(data);
return 0;
}
调试如图:
#include <stdio.h>
void test2(int a[3][5])
{
}
int main()
{
int arr[3][5] = { 1,2,3,4,5,2,3,4,5,6,3,4,5,6,7 };
test2(arr);
return 0;
}
调试如图:
调试过程中,要做到心里有数,也就是程序员自己心里要清晰的知道希望代码怎么执行,然后再去看代码有没有按照我们预定的路线在执行。
调试是需要反复去动手练习的,调试是可以增加程序员对代码的理解和掌控的,掌控了调试的能力,就能看到本质,就像能给程序做B超一样,对程序内部一览无余。
6、编程常见错误归类
6.1、编译型错误
编译型错误一般都是语法错误,这类错误一般看错误信息就能找到一些蛛丝马迹的,双击错误信息也能初步的跳转到代码错误的地方或者附近。编译错误,随着语言的熟练掌握,会越来越少,也容易解决。
6.2、链接型错误
看错误提示信息,主要在代码中找到错误信息中的标识符,然后定位问题所在。一般是因为:
- 标识符名不存在
- 拼写错误
- 头文件没包含
- 引用的库不存在
6.3、运行时错误
运行时错误,是千变万化的,需要借助调试,逐步定位问题,调试解决的是运行时问题。
完。