• 新手一定要掌握的实用调试技巧(vs2019)


     

    目录 

    1、什么是bug?

    2、调试是什么?

            2.1、调试是什么

            2.2、调试的基本步骤

            2.3、Debug和Release的介绍

    3、Windows环境调试介绍

            3.1、调试环境的准备

            3.2、学会快捷键

            3.3、调试的时候查看程序当前信息

                    3.3.1、查看临时变量的值

                    3.3.2、查看内存信息

                    3.3.3、查用调用堆栈

                    3.3.4、查看汇编信息

                    3.3.5、查看寄存器信息

    4、调试的实例 

    6、如何写出好(易于调试)的代码

            6.1、优秀的代码

            6.2、示范

            6.3、const的作用

    如何使用const和理解const int* 和int *const 的区别

    7、编程常见的错误

            7.1、编译型错误

            7.2、链接型错误 

     7.3、运行时错误


     

    1、什么是bug?

    BUG的英文释义是“虫子”,现在人们将再电脑系统或程序中,隐藏着的一些未被发现的缺陷或问题统称为bug(漏洞),人类历史上第一个程序BUG就是与虫子有关。

    1937年,一个年轻的美国小伙找到IBM公司要了200万叨做计算机,第一台成品取名为“马克1号”,写代码的是一个小妮·雷斯·霍波。有一天,他在调试程序时出现故障,经过一阵子周折,发现有只飞蛾被烤糊在两个继电器触电的中间导致短路。于是把程序故障统称为“臭虫BUG”。从此这只虫子名垂千古,永远的保存在了华盛顿的美国国家历史博物馆中

    2、调试是什么?

            2.1、调试是什么

    一般指的你写的代码,在Debug模式下编译以后,编译器在你的代码里插入了调试信息,你可以一步一步运行程序,查看中间结果,适用于你程序运行不对,需要检查中间过程确定问题源头的时候。

            2.2、调试的基本步骤

    • 发现程序错误的存在
    • 以隔离、消除等方式对错误进行定位
    • 确定错误产生的原因
    • 提出纠正错误的解决办法
    • 对程序错误予以改正,重新测试

            2.3、Debug和Release的介绍

    • Debug 通常称为调试版本,它包含调试信息,并且不作任何优化,便于程序员调试程序
    • Release 称为发布版本,它往往是进行了各种优化,使得程序在代码大小和运行速度上都是最优的,以便用户很好地使用
    • 可在编译器此处选择配置
    • 171cdcd7377243d8b72a8a6912bb5c5c.png

     

    3、Windows环境调试介绍

            3.1、调试环境的准备

    在环境中选择 debug 选项,才能使代码正常调试

            3.2、学会快捷键

    下面是调试常用的快捷键:

    F5: 启动调试,经常用来直接跳到下一个断点处(若没有断点将运行整个程序)
    F9:创建断点和取消断点。断点的重要作用,可以在程序的任意位置设置断点。这样就可以使得程序在想要的位置随意停止执行,继而一步步执行下去
    F10:逐过程,通常用来处理一个过程,一个过程可以是一次函数调用,或者是一条语句
    F11:逐语句,就是每次都执行一条语句,但是这个快捷键可以使执行逻辑进入函数内部,这是最常用的
    Ctrl+ F5:开始执行不调试,如果你想让程序直接运行起来而不调试就可以直接使用

            3.3、调试的时候查看程序当前信息

    注意:以下所有内容都是在调试开始后才可操作的

                    3.3.1、查看临时变量的值

    cc170eee0918426fa65525c87a0ce27d.png

    四个监视窗口都可以用,只需要在监视名称是输入合法的监视内容即可

    c07e46fedf7d432bbe8ec8a4ef2e2577.png

                    3.3.2、查看内存信息

    e879008de5344e3c9b251c7a88891c4b.png

    与监视窗口同理,四个口都可以用

    bcedb6e942bb437c889df2138489acf1.png

    在地址处输入你要查询的地址,在列处可以自己进行选择

    690dbde14010487383ced8ed6382b13f.png

    选定后回车即可查看 

    4247fb9fa946496591c84f73456eda32.png

                    3.3.3、查用调用堆栈

    abb12ddb954f4932852c212809b81c1b.png

    f05a2b57f29c4f5694a1a69ab51e1376.png

                    3.3.4、查看汇编信息

    f9e764a6aa064d6cb9f64d94def63dde.png

                    3.3.5、查看寄存器信息

    8b48c3db6ebe4e62a9b16500aefdfdeb.png

    4、调试的实例 

    1. #include <stdio.h>
    2. int main()
    3. {
    4. int i = 0;
    5. int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
    6. for (i = 0; i <= 12; i++)
    7. {
    8. arr[i] = 0;
    9. printf("hehe\n");
    10. }
    11. return 0;
    12. }

    调试如下

    863df43543e54904a3ecf533667cdb79.png

    6、如何写出好(易于调试)的代码

            6.1、优秀的代码

    优秀的代码应具有:1.代码运行正常

                                     2.bug很少

                                     3.效率高

                                     4.可读性高

                                     5.可维护性高

                                     6.注释清晰

                                     7.文档齐全

    我们常见的coding技巧有:1.使用assert

                                               2.尽量使用const

                                               3.养成良好的编码风格

                                               4.添加必要的注释

                                               5.避免编码陷阱

            6.2、示范

    对于上述我们所说coding技巧我们将在下面这个例子进行实现

    这里呢我们模拟实现库函数:strcpy

    首先呢,我们先看一下这个函数的参数和返回值类型

    6ee3ba3ed04b49a4bc030b07feeba4a1.png

     返回值类型为char*,参数都为char*,const后续会讲到,接下来我们进行实现,先写出主函数如下:

    1. //int main()
    2. //{
    3. // char arr1[] = "hello bit";
    4. // char arr2[20] = "xxxxxxxxxxxxx";
    5. // printf("%s\n", my_strcpy(arr2, arr1));
    6. // return 0;
    7. //}

    当我们不知道使用const时我们的写法是这样的

    1. //char* my_strcpy(char* dest, char * src)
    2. //{
    3. // char* ret = dest;//由于后面++操做会使dest所指向的地址发生变化,所以这儿提前将首地址存入ret
    4. // while (*dest = *src)
    5. // {
    6. // dest++;
    7. // src++;
    8. // }
    9. // return ret;//返回ret,也就是最初的dest,方便后续printf打印
    10. //}

    而这个代码其实我们还可以进行优化,优化如下

    1. //char* my_strcpy(char* dest, const char * src)
    2. //{
    3. // char* ret = dest
    4. // while (*dest++ = *src++) //进行了优化
    5. // ;//空语句
    6. // return ret;
    7. //}

     但是呢使用者不知道我们应该怎么给参数,如果使用者一不小心给了一个空指针呢比如下面

    03f88d13c19e43f8a5b3abc184452e59.png

     我们发现代码并没有报错,还是走了下去,但这是不是我们需要的呢?显然不是,这时候我们就需要使用前面所讲的assert(断言),搭配头文件#include 使用

    断言(assertion)是一种在程序中的一阶逻辑(如:一个结果为真或假的逻辑判断式),目的为了表示与验证软件开发者预期的结果——当程序执行到断言的位置时,对应的断言应该为真。若断言不为真时,程序会中止执行,并给出错误信息

    使用如下

    1. char* my_strcpy(char* dest, const char * src)
    2. {
    3. // //断言
    4. assert(dest != NULL);
    5. assert(src != NULL);
    6. char* ret = dest;
    7. while (*dest++ = *src++) //进行了优化
    8. ;//空语句
    9. return ret;
    10. }

    我们测试一下看一下使用效果

    e9d2d2162fa94d3a87ad56bca882035e.png

    我们发现它不在不作为,而是给你报出你的代码在什么路径下哪里出现错误,我们这里显示第8行出现错误,而我们地8行接受到了空指针, 所以程序会中止执行,并给出错误信息

    5456fff5010443bfba9ae6afb74182a4.png

    当我们搞定这些之后我们又出现了问题, 有个粗心的小伙伴在实现时,把*dest++ = *src++写反了,写成了 *src++=*dest++ ,此时我们发现程序正常运行,但给出的结果时错误的,者我们该怎么解决呢?这时候就需要用到我们接下来所要讲到的const了

            6.3、const的作用

    const是一个C语言(ANSI C)的关键字,具有着举足轻重的地位。它限定一个变量不允许被改变,产生静态作用。使用const在一定程度上可以提高程序的安全性和可靠性。另外,在观看别人代码的时候,清晰理解const所起的作用,对理解对方的程序也有一定帮助。

    const修饰的数据类型是指常类型,常类型的变量或对象的值是不能被更新的。

    主要作用
    1、可以定义const常量,具有不可变性
    2、便于进行类型检查,使编译器对处理内容有更多了解,消除了一些隐患。例如: void f(const int i) { …} 编译器就会知道i是一个常量,不允许修改;
    3、可以保护被修饰的东西,防止意外的修改,增强程序的健壮性。
    4、节省时间和提高效率,编译器通常不为普通const常量分配存储空间,而是将它们保存在符号表中,这使得它成为一个编译期间的常量,没有了存储与读内存的操作,使得它的效率也很高。

    我们先简单看一下吧

    0f98698f6449445abb70f25cba5fadba.png

    我们发现在对n进行修改时,编译器进行报错了,因为n不能被更新

    如何使用const和理解const int* 和int *const 的区别

    以一个简单的代码例子来解释

    1. #include <stdio.h>
    2. void test1()
    3. {
    4. int n = 10;
    5. int m = 20;
    6. int *p = &n;
    7. *p = 20;
    8. p = &m;
    9. }
    10. void test2()
    11. {
    12. int n = 10;
    13. int m = 20;
    14. const int* p = &n;
    15. *p = 20;
    16. p = &m;
    17. }
    18. void test3()
    19. {
    20. int n = 10;
    21. int m = 20;
    22. int *const p = &n;
    23. *p = 20;
    24. p = &m;
    25. }
    26. int main()
    27. {
    28. //测试无cosnt的
    29. test1();
    30. //测试const放在*的左边
    31. test2();
    32. //测试const放在*的右边
    33. test3();
    34. return 0;
    35. }

    6f6369528d6e49c7836b25116fdd44a7.png  

    我们测试后发现 

    test1()函数可以正常运行,而test2 ( )和test3()不可以。
    test2()中的const int* p = &n;的const的作用是使指针不能改变指向当前地址的值,也就是说,此时n等于10,不能通过指针p来改变n的值。*p=20;此时这句话会报错。
    test3()中的int *const p = &n;的const的作用是使指针不能改变当前所指向的地址。也就是说,此时p指向的是n的地址,而不能再改变去指向m的地址。p = &m; 这句话会报错

    总结如下

    /const 修饰指针的时候
     129当const 放在*的左边的时候,限制的是指针指向的内容,不能通过指针变量改变指针指向的内容,但是指针变量的本身是可以改变的
     130当const 放在*的右边的时候,限制的是指针变量本身,指针变量的本身是不能改变的,但是指针指向的内容是可以通过指针来改变的

    则my_strcpy()函数完整实现如下

    1. //char* my_strcpy(char* dest, const char * src)
    2. //{
    3. // char* ret = dest;
    4. // //断言
    5. // assert(dest != NULL);
    6. // assert(src != NULL);
    7. //
    8. // while (*dest++ = *src++)
    9. // ;//空语句
    10. //
    11. // return ret;
    12. //}

    7、编程常见的错误

            7.1、编译型错误

    直接看到错误提示信息(双击),解决问题。或者凭借经验就可以搞定。相对来说简单

    956729e8cd7b4904a175209cd3a768be.png

    双击错误就可以跳到问题行 

            7.2、链接型错误 

    看错误提示信息,主演是在代码中找到错误信息中的标识符,然后定位问题所在。一般标识符不存在或者拼写错误

    8c30076647334d7eb0c15deff3649043.png

    如上述拼写错误,我们进行编译后发现

     7.3、运行时错误

    借助调试,逐步定位问题,最难搞。需要慢慢积累经验。

     

    制作不易,一键三连!!一起加油!!!

     

     

     

     

  • 相关阅读:
    leetcode(c++)-买卖股票的最佳时机 II
    Java面试题汇总
    C++-继承-单继承-虚函数-内存结构分析-汇编分析-逆向分析(一)
    JavaScript流程控制-循环(循环(for 循环,双重 for 循环,while 循环,do while 循环,continue break))
    Oracle Install 19c@CentOS 7.6
    LeetCode中等题之递增子序列
    C/C++程序,从命令行传入参数
    编码中的Adapter,不仅是一种设计模式,更是一种架构理念与解决方案
    1024发博客!
    互联网医院|互联网医院系统引领医疗科技新风潮
  • 原文地址:https://blog.csdn.net/m0_71731682/article/details/130793600