【vs2019】调试介绍
文章目录
1. 调试是什么
调试(debug)是发现和减少
计算机程序和电子仪器设备中得程序性错误
2. 调试的基本步骤
- 发现程序错误的存在
- 以隔离、消除等方式对错误进行定位
- 确定错误产生的原因
- 提出纠正错误的解决办法
- 对程序错误予以改正,重新测试
3. 调试的环境
并不是在任何环境下都可以进行调试
源程序通常有两种版本
- Debug版本
- Release版本
Debug版本称为调试版本,包含调试信息,并且不做任何优化,便于程序员调试程序
Release版本称为发布版本,它往往进行了各种优化,使得程序在代码大小上和运行速度上都是最优的,便于用户使用
只有在Debug版本下,程序才可以进行调试
代码在
Debug环境下的结果展示
代码在Release环境下的结果展示
在这里插入图片描述
代码在Debug环境下的反汇编
代码在Release环境下的反汇编
可以看到Release环境下确实会对代码进行优化
那么Release版本和Debug版本究竟进行了哪些优化?
- 优化编译选项:Release版本的编译选项通常会启用更多的优化选项,例如去除符号表、优化调试信息、优化代码大小等,这些都有助于减小程序体积、提高程序的运行效率。
- 剔除调试代码:Debug版本通常会包含一些额外的调试代码,例如assert、日志输出等,这些代码对程序的运行效率可能会产生一定的影响。在Release版本中,这些调试代码通常会被剔除,从而提高程序的运行效率。
- 优化代码结构:在Debug版本中,编译器会生成一些额外的代码,例如调试信息、符号表等,这些代码可能会导致代码结构比较松散。在Release版本中,编译器会进行优化,将代码结构优化得更加紧凑,从而提高程序的运行效率。
- 禁用断言:Debug版本通常会包含一些断言代码,这些代码会在程序运行时进行检查,如果检查失败会导致程序崩溃。在Release版本中,这些断言代码通常会被禁用,从而减小程序体积、提高程序的运行效率。
来看下面这个代码在
Release版本和Debug版本下有什么不同?
#include <stdio.h>
int main()
{
int i = 0;
int arr[10] = {0};
for(i=0; i<=12; i++)
{
arr[i] = 0;
printf("hehe\n");
}
return 0; }
Debug版本下是死循环
Release版本下不是死循环
我们观察在Debug版本下的各个变量的地址
我们发现在Debug版本下arr[12]的地址就是i的地址,也就是说arr[12]和i是同一个变量,当i==12时,arr[12]=0相当于改变了循环变量,在循环体内改变循环变量的值极有可能发生死循环
我们可以大胆分析以下在Debug模式下变量的内存图如下
在Debug版本下,先定义谁就先开辟谁的空间,至于每个变量之间空多大位置取决于编译器,MSVC编译器变量i的地址比arr[9]的地址多12字节
我们在来观察一下在
Release版本下变量的地址
Release版本不能调试,所以用printf打印出来
我们可以推测在Release版本下,内存布局是这样的
在这种特殊情况下,Release版本下编译器会对变量的定义的顺序发生改变,编译器会先定义arr数组,再定义i
注:上面两种版本的内存布局是这样基于两个原因
- 定义的局部变量存储在栈区,而栈区空间使用规则是先用高地址,后用低地址
- 数组元素随着下标增长,地址由低到高
4. VS2019调试快捷键
F5
未启动调试前,开始调试并跳到第一个间断点(如果有),没有则不会进行调试
启动调试后,跳到下一个间断点(如果有),没有则推出调试
F9
创建断点和取消断点
断点的重要作用,可以在程序的任意位置设置断点。
这样就可以使得程序在想要的位置随意停止执行,继而一步步执行下去。
F10
逐过程,通常用来处理一个过程,一个过程可以是一次函数调用,或者是一条语句。
F11
逐语句,就是每次都执行一条语句,但是这个快捷键可以使我们的执行逻辑进入函数内部(这是最
长用的)。
CTRL + F5
开始执行不调试,如果你想让程序直接运行起来而不调试就可以直接使用。
更多快捷键点我
注:按上述快捷键无效果,则需要同时按住Fn和上述快捷键
这里有一个小技巧,当我们想调试观察循环第n次的情况时,我们可以设置
条件断点,条件断点的意思是当满足条件时,才可以跳到断点处
设置方式
1->在循环体内打上断点,并且右击断点
2->选择条件选项,并且设置条件,如想直接从第10次循环开始调试,直接将条件设置为i==10
3->按下回车保存
4->开始调试,这样按F5后直接跳到条件断点处,此时的i等于10
5. vs2019调试窗口
注:调试窗口均是在开启调试后才能开启的
1.查看临时变量的值->自动窗口
作用:观察上下文出现过变量的值
打开方式
2.查看变量的地址->内存
作用:查看变量的内存信息
打开方式
注:若没有内存选项,则需要手动开启
开启方式 工具->选项->调试->打开地址级调试
3.查看调用堆栈->调用堆栈
作用:清晰的反应函数的调用关系以及当前调用所处的位置
打开方式
4.查看程序的汇编代码->反汇编
打开方式:程序开启调试后直接在代码区域右击鼠标,选中反汇编
6. 写代码的注意事项
优秀的代码应该是这样
- 代码运行正常
- bug很少
- 效率高
- 可读性高
- 可维护性高
- 注释清晰
- 文档齐全
6.1 Coding技巧
- 使用assert
- 尽量使用const’
- 养成良好的编码风格
- 添加必要的注释
- 避免编码的陷阱
6.2 assert

assert用来检测一个表达式是不是0,如果表达式是0则调用abort函数终止程序并且在标准错误流上输出一段话,这句话包含值是0的表达式和该程序所在的路径以及assert所在的行数
使用assert需要包含头文件assert.h
我们不能解引用空指针,所以
assert可以用来判断该指针是否为空指针
#include <stdlib.h>
#include <assert.h>
int main()
{
long long* p = (long long*)malloc(sizeof(int) * 100000000l);
assert(p);
*p = 10;
printf("%lld\n", *p);
return 0;
}
6.3 const
在C语言中,
const修饰的变量叫做常变量,它们本质上是变量,只不过具有只读性
- const直接修饰变量,无法直接修改变量
可以通过该变量的指针来修改该变量的值
- const修饰指针变量有两种情况
- const在*前面
代表const修饰的是指针变量所指向的对象,指向的对象不可以直接修改
这样并不是很严谨,因为定义时没有规定a被const修饰的,但是定义指针变量p时又规定了a被const修饰- const在*后面
代表const修饰的是指针变量本身,则这个指针变量不能指向其他的变量
可以通过二级指针改变p本身的值
7. 实现strcpy,strlen函数


注:实参是
chat*类型,形参是const char*类型,可以把不安全的类型传给更安全的类型
如果将const char*类型传给char*类型的话,编译器会报警告
8. 编程常见的错误
8.1 编译型错误
直接看错误提示信息(双击),解决问题。或者凭借经验就可以搞定。相对来说比较简单。

8.2 链接型错误
看错误提示信息,主要在代码中找到错误信息中的标识符,然后定位问题所在。一般是函数名不存在或者拼写错误
LNK代表LINK链接,说明是链接错误
8.3 运行时错误
借助调试,逐步定位问题


























