12.0、C语言——实用调试技巧
调试的基本步骤:
发现程序错误的存在
以隔离、消除等方式对错误进行定位
确定错误产生的原因
提出纠正错误的解决方法
对程序错误予以改正,重新测试
Debug和Release的介绍:
Debug通常称为测试版本,它包含调试信息【所以从文件内存大小上看,Debug版本也比Release要大】,并且不作任何优化,便于程序员调试程序
Release称为发布版本,他往往是进行了各种优化,使得程序在代码大小和运行速度上都是最优的,以便很好的使用【不能像Debug那样一行一行的执行调试】
写好代码点击运行后,会发现代码文件夹中多了一个Debug文件夹,点进去可以看到一个.exe文件,双击可以运行:
那么当我们选择Release再运行一次代码后,会发现多了一个Release文件夹,里面也有一个.exe文件,双击之后可以运行~
学会使用快捷键:
vs最常用的几个快捷键:
F5
启动测试,经常用来直接调到下一个断点处,再按一下会调到执行逻辑的下一个断点处【通常与F9配合使用】
F9
创建断点和取消断点,断点的重要作用,可以在程序的任意位置设置断点,这样就可以使得程序在想要的位置随意停止执行,继而一步步执行下去【通常与F5配合使用】
F10
逐过程,通常用来处理一个过程,一个过程可以是一次函数调用,或者是一条语句,【比如遇到函数的时候,不会进入到函数内部去,而是直接执行函数,然后返回继续向下执行】
F11
逐语句,就是每次都执行一条语句,但是这个快捷键可以使我们的逻辑 进入函数内部(这是最常用的),当然如果进入函数内部后想要跳出来用 shift + f11
Ctrl + F5
开始执行不调试,如果你想让程序直接运行起来而不调试就可以直接使用
下面来看一个程序:
int main() {
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
for (int i = 0; i <= 12;i++) {
printf("hehe\n");
arr[i] = 0;
}
return 0;
}
运行后的结果是陷入死循环一直打印 hehe ,但是为什么呢?
首先来看一下在 该main()函数 中 ,栈空间的存储方式:
那么存储的方式就是这么恰巧,数组 arr[] 分配的内存空间就在变量 i 空间的后面两个,那么当我们数组访问越界到第三次也就是 i = 12 的时候,刚好此时i的地址和 arr[12] 的地址相同,所以当我们将 arr[12] 置为 0 的时候同时也将 i 置为 0 了,导致程序陷入死循环;
如果将 for 循环条件改为 i<=11 那么就不会陷入死循环而是报 访问越界的错误;
如果将 for 循环条件改为 i<=13或者更大的数字也会陷入死循环,这是肯定的因为12就已经死循环了,比12大肯定也会进入死循环~
【当然在不同的编译器中死循环的位置也不相同,因为每个编译器的的内存布局有所差异】
在运行上述代码的时候用的是debug版本运行的;
当我们用release版本运行程序的时候,发现并不会出现死循环的现象,为什么呢?
这是因为release帮我们做了一定的优化,通过查看栈空间的内存可以发现,release将变量 i 的定义放到了定义数组 arr 的后面,这样一来就不会陷入死循环~
assert断言:
我们先来写一个字符串拷贝的函数 my_strcpy() 如下:
void my_stycpy(char* ori,char* dest) { //将原来的字符ori拷贝到目标字符数组dest
assert(dest != NULL); //断言
assert(ori != NULL); //断言
while (*dest++ = *ori++) {
;
}
}
在字符串拷贝之前先用 assert() 断言 判断一下这两个指针是否为空指针,如果为空指针就会抛出这个异常,让程序猿能够看见~方便调试~
在使用 assert() 的时候记得引入头文件 #include <assert.h>
const 关键字修饰指针变量:
用 法 一:
int main() {
int num = 10;
const int* p = #
*p = 20;
}
当 const 放在 " * " 前面时,那么可以看做 const 修饰的是 *p , 所以第三条语句*p = 20; 是错误语句,不可以修改 *p 的值【此时修饰的是*p,限制的是 *p】
用 法 二:
int main() {
int num = 10;
int* const p = #
int a = 20;
p = &a;
}
当 const 放在 " * " 后面时,那么可以看做 const 修饰的是 p , 所以第四条语句 p = &a; 是错误语句,不可以修改 p 的值【此时修饰的是指针变量本身,限制的是 p】
那么给大家解释完 const 的用法过后,再来优化一下之前写好的 my_strcpy(const char* ori,char* dest) 函数,
char* my_stycpy(const char* ori,char* dest) {
char* ret = dest;
assert(dest != NULL); //断言
assert(ori != NULL); //断言
while (*dest++ = *ori++) {
;
}
return ret;
}
int main() {
char arr1[] = "###########";
char arr2[] = "hello";
printf("arr1[] = %s", my_stycpy(arr2, arr1));
}
该函数是将原字符 ori 拷贝到 dest 目标字符上去,所以我们的原字符肯定是不能够被改变的,所以这里给 ori 指针变量加上 const 关键词修饰会后,*ori 则受到限制不能被改变了;
那么最后返回值的部分也从 void 改成了 char*,返回的是目标字符数组的首地址,这样方便我们调用完 my_stycpy() 函数之后查看目标数组是否改变~