VS编译系统 实用调试技巧

news2024/11/25 14:28:14

目录

  1. 什么是bug?

  1. 调试是什么?有多重要?

  1. debug和release的介绍

  1. windows环境调试介绍、

  1. 一些调试实例

  1. 如何写出(易于调试)的代码

  1. 编程常见的错误

  1. 什么是bug?

其实bug在英文翻译中有表示臭虫的含义,因为第一次被发现的导致计算机程序错误的是飞蛾,也是第一个计算机程序错误

  1. 调试是什么?有多重要?

写代码就是破案的过程,错误都是有迹可循的。迹象越多就越容易顺藤而上,这就是推理的途径。

发现错误-->调试-->解决错误

2.1调试的基本步骤:

  1. 发现程序错误的所在

  1. 以隔离、消除等方式对错误进行定位

  1. 确定错误产生的原因

  1. 提出错误的解决办法

  1. 对程序错误予以改正、重新测试

  1. Debug和release的介绍(掌握)

Debug通常称为 调试版本,它包含调试信息,并且不做任何优化,便于程序员调试。
Release称为 发布版本,它往往是进行了各种优化,使得程序在代码大小和运行速度上都是最优的,以便于用户很好的使用。

Debug和Release的区别就是Release的速度更快,所占的内存更少,但是Release不能调试,Debug可以进行调试。

  1. windows环境调试介绍

3.1调试环境的准备

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

3.2调试快捷键
1.F5 - 开始调试
2.ctrl + F5 开始执行(不调试)
3.F9 设置断点/取消断点
4.F10 逐过程
5.F11 逐语句

使用Fn辅助功能键

F10和F11有什么区别呢?
他们在执行的过程F10是逐过程进行的,F11是逐语句进行的,所以在函数的调用时,F11可以更加细节的进入到函数的内部进行逐语句的调试。

F5是和F9配合使用的
F9是设置断点,F5是开始执行并跳到第一个断点

举例:

光标放到16行,按Fn+F9,我们就在16行加了一个断点,意思是F5开始调试直到断点处停止调试。

按F5开始调试

得到了我们的程序执行结果

在以上的基础上继续按F5开始调试结果会是什么样的呢?

在循环里面的断点,每次循环都会继续停到断点处。


抛开上面我们所说的,依然是下面的程序:

如果循环的次数很大,达到几百上千时,而我们的错误刚好在第400次循环时该怎么办呢?

在VS编译器中我们也可以设置断点。

如果我们不明白条件断点,我们也可以在代码中加上一条条件语句,之后将断点打在条件语句处,如果满足条件则断点被触发。

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

如果我们想在调试过程中观察i的值,点击调试->窗口->自动窗口

在我们按F10进行逐过程调试时,自动窗口会自动的显示我们在这个过程中,当前程序的信息。

如果我们想在调试过程中观察程序的局部变量的变化,点击调试->窗口->局部变量

如果我们想在调试过程中监视任何变量,点击调试->窗口->监视

监视不仅可以监视变量的值,也可以监视变量的地址,表达式的值

内存

调用堆栈

就是数据结构里面的栈。

反映数据的调用逻辑。

  1. 调试实例

求1!+2!+......+10!

int main()
{
    int n = 0;
    int sum = 0;
    scanf("%d", &n);
    int ret = 1;
    for (n = 1; n <= 10; n++)
    {
        for (int i = 1; i <= n; i++)
        {
            ret *= i;
        }
        sum += ret;
    }
    for (int i = 1; i <= n; i++)
    {
        ret *= i;
    }
    printf("%d\n", sum);
    return 0;
}

经过计算,我们发现我们的代码是错误的,错误在于我们每一次循环没有及时修改ret的值为1。

但是如果我们不能一眼看出来错误在哪里,还是要依据调试来解决问题的。

1.在监视窗口中添加我们想要监视的变量

2.F11逐语句并且观察监视窗口中变量的值。

我们在调试的过程中发现ret在执行过程中,继续进行阶乘运算时,初始值不是1,这就是我们通过调试发现的问题所在,经过修改,代码如下:

int main()
{
    int n = 0;
    int sum = 0;
    //scanf("%d", &n);
    int ret = 1;
    int i = 0;
    for (n = 1; n <= 10; n++)
    {
        ret = 1;
        for (int i = 1; i <= n; i++)
        {
            ret *= i;
        }
        sum += ret;
    }
    for (int i = 1; i <= n; i++)
    {
        ret *= i;
    }
    printf("%d\n", sum);
    return 0;
}
  1. 如何写出(易于调试)的代码

  1. 代码运行正常

  1. bug很少

  1. 效率高

  1. 可读性高

  1. 可维护性高

  1. 注释清晰

  1. 文档齐全

常见coding技巧

  1. 使用assert

  1. 尽量使用const

  1. 养成良好的编码风格

  1. 添加必要的注释

  1. 避免编码的陷阱

示例:模拟实现strcpy函数

void my_strcpy(char* dest, char* scr)
{
    while (*scr != '\0')
    {
        *dest = *scr;
        dest++;
        scr++;
    }
    *dest = *scr;//'\0‘的拷贝
}
int main()
{
    char arr1[20] = "hello world";
    char arr2[40] = { 0 };
    my_strcpy(arr2, arr1);
    printf("%s\n", arr2);
    return 0;
}

但是在我们看来这样的代码并不是一个好的代码,我们可以继续将代码进行优化。

my_strcpy函数部分我们可以将它改为以下形式

void my_strcpy(char* dest, char* scr)
{
    while (*scr != '\0')
    {
        *dest++ = *scr++;
        //dest++;
        //scr++;
    }
    *dest = *scr;//'\0‘的拷贝
}

除了上述的代码形式外,我们还可以将代码改为以下形式:

void my_strcpy(char* dest, char* scr)
{
    while (*dest++ = *scr++)
    {
        ;
    }
   
}
int main()
{
    char arr1[20] = "hello world";
    char arr2[40] = { 0 };
    my_strcpy(arr2, arr1);
    printf("%s\n", arr2);
    return 0;
}

但是我们在传递参数的时候不能够保证我们传递过来的指针是否为空指针,也不能保证指针的有效性,所以在这个时候我们就要使用assert断言,来保证指针的有效性。

为什么使用assert断言?
如果在传参的这个过程中,我们真的传递了一个空指针,或者说是一个无效的指针,那么使用断言就会提示我们这样的问题所在,但是如果你没有使用断言来判断指针的有效性,整个程序运行起来的最终结果就会崩掉,并且同时我们也不会知道它为什么会运行失败。
#include <assert.h>
void my_strcpy(char* dest, char* scr)
{
    //assert(dest != NULL);
    //assert(scr != NULL);
    assert(dest && scr);
    while (*dest++ = *scr++)
    {
        ;
    }
   
}
int main()
{
    char arr1[20] = "hello world";
    char arr2[40] = { 0 };
    my_strcpy(arr2, arr1);
    printf("%s\n", arr2);
    return 0;
}
当然断言不仅仅是应用于指针的判断,断言是依据assert后面表达式真假而言的。对于我们程序员来讲是一个很好的编程习惯。

other:

我们在C语言的库中发现strcpy函数和我们自己写的my_strcpy函数有所出入。详细请看下图:

使用const修饰和不使用const又有什么区别呢?

const修饰指针的作用

  1. const放在*的左面,const int* p或者int const* p,const放在*的左面限制的是*p而不是p。

*p不能改,是p指向的内容;但是p可以改,p可以指向其他变量

int main()
{
    const int m = 0;
    int n = 0;
    //m = 20;err
    const int* p = &m;
    //*p = 20;错误
    p = &n;//ok
    return 0;
}
  1. const放在*的右面,int* const p ,如果const放在*右边,限制的是p而不是*p,*p可以改,p不可以改。

int main()
{
    int m = 0;
    int n = 0;
    int* const p = &m;
    *p = 20;//ok
    p = &n;//no
    return 0;
}

在我们strcpy中是如何使用的呢?

如果我们将源头和目的地写反,就出现了一个bug,这个时候使用const修饰char* scr就可以避免这个错误。提高了代码的健壮性。

#include <assert.h>
void my_strcpy(char* dest, const char* scr)//意思是*scr不能被改变
{
    //assert(dest != NULL);
    //assert(scr != NULL);
    assert(dest && scr);
    while (*dest++ = *scr++)
    {
        ;
    }
   
}
int main()
{
    char arr1[20] = "hello world";
    char arr2[40] = { 0 };
    my_strcpy(arr2, arr1);
    printf("%s\n", arr2);
    return 0;
}
#include <assert.h>
char* my_strcpy(char* dest, const char* scr)//意思是*scr不能被改变
{
    //strcpy函数返回的是目标空间的起始地址
    assert(dest && scr);
    char* ret = dest;//记住起始空间的地址
    while (*dest++ = *scr++)
    {
        ;             
    }
    return ret;
}
int main()
{
    char arr1[20] = "hello world";
    char arr2[40] = { 0 };
    //链式访问
    printf("%s\n", my_strcpy(arr2, arr1));
    return 0;
}
  1. 编程常见的错误

6.1编译型错误

例如:缺少分号等语法错误,可以直接查看错误信息,解决问题。

6.2链接型错误

例如:拼写错误,或者标识符不存在提示有无法解析的外部命令。

主要在代码中找到错误信息的标识符,然后定位问题所在。

ctrl+f--搜索

6.3运行时错误

借助调试,逐步定位问题。

感谢阅读,欢迎大家批评指正!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/373577.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Linux驱动开发100问】什么是模块?如何编写和使用模块?

&#x1f947;今日学习目标&#xff1a;什么是Linux内核&#xff1f; &#x1f935;‍♂️ 创作者&#xff1a;JamesBin ⏰预计时间&#xff1a;10分钟 &#x1f389;个人主页&#xff1a;嵌入式悦翔园个人主页 &#x1f341;专栏介绍&#xff1a;Linux驱动开发100问 什么是模块…

堆的基本存储

一、概念及其介绍堆(Heap)是计算机科学中一类特殊的数据结构的统称。堆通常是一个可以被看做一棵完全二叉树的数组对象。堆满足下列性质&#xff1a;堆中某个节点的值总是不大于或不小于其父节点的值。堆总是一棵完全二叉树。二、适用说明堆是利用完全二叉树的结构来维护一组数…

css 画图之质感盒子

前言 css 众所周知可以做很多的事情&#xff0c;比如&#xff1a;界面效果、特效、独特的样式等。今天给各位朋友带来的是以box-shadow来画一个很有质感效果的一个盒子。 之前在网上冲浪的时候&#xff0c;发现了这样的一个效果&#xff0c;所以来记录一下。 下面是实现后的…

Zookeeper源码环境搭建

前言 一、IEDA导入zk源码 git clone -b release-3.7.0 gitgithub.com:apache/zookeeper.git二、切换到稳定分支 通过命令行切换zk分支到3.8.1稳定版。 git checkout -b branch-3.8.1三、编译项目 执行maven命令编译项目 mvn clean install -Dmaven.test.skiptrue三、集群搭…

【计算机网络】高并发业务必备的Linux网络IO模型

IO的操作也就是应用程序从TCP缓冲区中读取数据的时候。网络I/O的本质是socket的读取&#xff0c;socket在linux中被抽象为流&#xff0c;I/O可以理解为对流的操作。对于一次I/O访问&#xff0c;数据会先被拷贝到操作系统的内核的缓冲区中&#xff0c;然后才会从操作系统内核的缓…

Java EE|TCP/IP协议栈之应用层协议DNS详解

文章目录一、对DNS的感性认识简介特点一些常见疑问二、DNSDNS域名结构域名的分级三、域名服务器四、域名解析过程参考一、对DNS的感性认识 简介 DNS&#xff0c;即Domain Name System,是域名系统的简称。它是Internet上解决网上机器命名的一种系统。 TCP/IP中的IP地址是由四…

C语言结构体对齐

1. 结构体对齐 要点 变量只能存储在他的长度的整数倍地址上结构体整体对齐跟他的最长的字段整数倍对齐 栗子1 struct Example1 {char a; //1个字节int c; //4个字节short b; //2个字节 };std::cout << sizeof(Example1 ) << std::endl; // 12 std::cout &…

JVM篇之垃圾回收

一.如何判断对象可以回收 1.引用计数法 只要一个对象被其他变量所引用&#xff0c;就让它的计数加1&#xff0c;被引用了两次就让它的计数变成2&#xff0c;当这个变量的计数变成0时&#xff0c;就可以被垃圾回收&#xff1b; 弊端&#xff1a;当出现如下图的情况&#xff0…

4.OCR文本识别Connectionist Temporal Classification(CTC)算法

文章目录1.基础介绍2.Connectionist Temporal Classification(CTC)算法2.1 什么是Temporal Classification2.2 CTC问题描述2.2关于对齐2.3 前向后向算法2.4 推理时3.pytorch中的CTCLOSS参考资料欢迎访问个人网络日志&#x1f339;&#x1f339;知行空间&#x1f339;&#x1f3…

【react】react创建项目与引入AntD组件库:

文章目录一、初始化项目&#xff1a;【1】创建项目【2】暴露项目配置文件【3】安装依赖【4】配置less二、快捷键&#xff1a;【1】rcctab三、安装AntD组件库&#xff1a;【1】安装【2】index.js【3】问题&#xff1a;【4】效果&#xff1a;一、初始化项目&#xff1a; 【1】创…

【基于增强上下文注意网络:超分】

Enhanced Context Attention Network for Image Super Resolution &#xff08;基于增强上下文注意网络的图像超分辨率&#xff09; 深度卷积神经网络&#xff08;CNN&#xff09;极大地提高了图像超分辨率&#xff08;SR&#xff09;的性能。尽管图像随机共振的目标是恢复高频…

Mysql视图,存储过程,触发器,函数以及Mysql架构

一,视图视图是基于查询的一个虚拟表 , 也就是将sql语句封装起来, 要用的时候直接调用视图即可, select语句查询的表称为基表, 查询的结果集称为虚拟表, 基本表数据发生了改变, 那么视图也会发生改变, 使用视图就是为了简化查询语句.1.CREATE VIEW view_admin AS SELECT * FROM…

聊一聊过度设计!

文章目录什么是过度设计&#xff1f;过度设计的坏处如何避免过度设计充分理解问题本身保持简单小步快跑征求其他人的意见总结新手程序员在做设计时&#xff0c;因为缺乏经验&#xff0c;很容易写出欠设计的代码&#xff0c;但有一些经验的程序员&#xff0c;尤其是在刚学习过设…

top -p pid为什么超过100%

CPU&#xff1a;Cores, and Hyper-Threading 超线程&#xff08;Hyper-Threading &#xff09; 超线程是Intel最早提出一项技术&#xff0c;最早出现在2002年的Pentium4上。单个采用超线程的CPU对于操作系统来说就像有两个逻辑CPU&#xff0c;为此P4处理器需要多加入一个Logic…

Spring Cache的基本使用与分析

概述 使用 Spring Cache 可以极大的简化我们对数据的缓存&#xff0c;并且它封装了多种缓存&#xff0c;本文基于 redis 来说明。 基本使用 1、所需依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-…

redis秒杀

redis优惠券秒杀 为什么订单表订单ID不采用自增长&#xff1f; id规律性太明显&#xff0c;容易被用户猜测到&#xff08;比如第一天下订单id10&#xff0c;第二天下订单id100&#xff0c;在昨天的1天内只卖出90商品&#xff09;受单表数据量限制&#xff08;订单数据量大&am…

Redis高级删除策略与数据淘汰

第二章&#xff1a;Redis高级 学习目标 目标1&#xff1a;能够说出redis中的数据删除策与略淘汰策略 目标2&#xff1a;能够说出主从复制的概念&#xff0c;工作流程以及场景问题及解决方案 目标3&#xff1a;能够说出哨兵的作用以及工作原理&#xff0c;以及如何启用哨兵 …

《分布式技术原理与算法解析》学习笔记Day23

分布式数据复制 我们在进行分布式数据存储设计时&#xff0c;通常会考虑对数据进行备份&#xff0c;以提高数据的可用性和可靠性&#xff0c;“数据复制技术”就是实现数据备份的关键技术。 什么是数据复制技术&#xff1f; 在分布式数据库系统中&#xff0c;通常会设置主备…

Java StringBuilder类(为什么更高效、常用方法、构造器)

StringBuilder类一、引言二、StringBuilder类特点三、StringBuilder构造方法四、StringBuilder常用方法五、StringBuilder为什么高效率六、StringBuffer类一、引言 为什么要引入StringBuilder类 提高字符串操作效率&#xff0c;尤其是涉及大量字符串拼接 //获取1970年1月1日0时…

2023年三月份图形化一级打卡试题

活动时间 从2023年3月1日至3月21日&#xff0c;每天一道编程题。 本次打卡的规则如下&#xff1a; 小朋友每天利用10~15分钟做一道编程题&#xff0c;遇到问题就来群内讨论&#xff0c;我来给大家答疑。 小朋友做完题目后&#xff0c;截图到朋友圈打卡并把打卡的截图发到活动群…