VS实用调式技巧

简介: VS实用调式技巧



今天我们接着调式技巧介绍。

调式的实例

实例一

实现代码:求 1!+2!+3! ...+ n! ;不考虑溢出。

这时候我们如果3,期待输出9,但实际输出的是15。

why?这里我们就要用到调试技巧

  • 首先推测问题出现的原因。初步确定问题可能的原因最好。
  • 实际上手调试很有必要。
  • 调试的时候我们要思路清晰,看代码是否按照我们预期的在执行。
#include<stdio.h>
int main()
{
  int i = 0;
  int sum = 0;//保存最终结果
  int n = 0;
  int ret = 1;//保存n的阶乘
  scanf("%d", &n);
  for (i = 1; i <= n; i++)
  {
    int j = 0;
    for (j = 1; j <= i; j++)
    {
      ret *= j;
    }
    sum += ret;
  }
  printf("%d\n", sum);
  return 0;
}

经过调试我们发现原因是:ret没有每次进入for循环的时候初始化为1,在1的基础上去累乘。

#include<stdio.h>
int main()
{
  int i = 0;
  int sum = 0;//保存最终结果
  int n = 0;
  int ret = 1;//保存n的阶乘
  scanf("%d", &n);
  for (i = 1; i <= n; i++)
  {
    int j = 0;
    ret = 1;
    for (j = 1; j <= i; j++)
    {
      ret *= j;
    }
    sum += ret;
  }
  printf("%d\n", sum);
  return 0;
}

实例二

实现代码:循环打印10次hehe。

但是我们在VS上发现程序在死循环打印hehe。

在其他编译器也出现了不同的情况打印了12次hehe

//注意这个代码的验证环境VS底下,X86环境
#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;
}

调试过后,我们发现当我们把arr[12]设置为0的时候,同时我们的i也变回到了0。这样就导致了死循环。我们不禁猜测:arr[12] i 使用的是同一块地址

通过监视窗口,我们发现果然arr[12]和i使用的是同一块地址

我们画图来让大家更易于理解

导致死循环的原因:

  1. 在栈区上内存使用的习惯时:从高地址向低地址处使用
  2. 数组随着下标的增长,地址是由低到高变化的

巧合arr[12]和i使用就是同一块地址。代码中arr和i之间空2个整型,完全是巧合(取决于编译器)VC i和arr之间没有空隙,gcc i和arr之间空1个整型。所以说完全是巧合,取决于编译器。这个代码是严格依赖环境的。未来再次遇到这样的代码,我们就会向着方向去分析,数组越界和死循环。

还有同学这样修改我们的代码不导致死循环。

#include <stdio.h>
int main()
{
  int arr[10] = { 0 };
  int i = 0;
  for (i = 0; i <= 12; i++)
  {
    arr[i] = 0;
    printf("hehe\n");
  }
  return 0;
}

按照代码执行的逻辑顺序关系在栈区上创建的临时变量来说,这样是可以的消除死循环。但是不太符合我们正常思维。

在上一篇博文我们提到Dug和Release版本。在这里有什么不用呢?

Dug死循环

Release优化

我们清晰的发现了,release版本将我们的 临时变量 i 创建在栈区的时候,放在arr后面,这样越界访问也不会访问到i的地址了 ,真的非常聪明!

实例小游戏

我们在学习函数和数组的时候写了两个小游戏:

C语言之三子棋游戏实现篇_唐唐思的博客-CSDN博客

C语言之扫雷游戏实现篇-CSDN博客

像这种较复杂的代码怎么去调试呢?

  • 理清楚代码逻辑,想清楚代码应该怎么走
  • F10进去函数内部调试
  • 看代码有没有按照自己的预期去执行
  • 一维数组和二维数组传参访问

大家一定要自己动一动小手去调试代码,这是灰常灰常重要的!!!!

一维数组和二维数组传参访问的tip

数组名,下标

这样就可以查看每一行里的元素,而不仅仅是只看得到每一行第一个元素。

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

优秀代码特征

  • 代码运行正常
  • Bug很少
  • 效率高
  • 可读性高
  • 可维护性高
  • 注释清晰
  • 文档齐全

常见的coding技巧

  • 使用assert(断言)
  • 尽量使用const
  • 养成良好的编码风格(变量名的命名体现)
  • 添加必要的注释
  • 避免编码的陷阱

示例1:模拟实现strcpy函数

strcpy - C++ Reference (cplusplus.com)

strcpy的使用

#include<stdio.h>
int main()
{
  char arr1[] = "xxxxxxxxxxxxxx";
  char arr2[] = "abcdef";//注意'\0'也会拷贝过去
  strcpy(arr1, arr2);
  printf("%s", arr1);
  return 0;
}

strcpy模拟代码

关于模拟实现strcpy,在前面我们也非常详细的讲解了

戳这里!C语言之字符函数&字符串函数篇(1)-CSDN博客

#include<stdio.h>
char* my_strcpy(char* des, char* src)//目的地址,源地址
{
  char* ret = des;
  while (*src != '\0')
  {
    *des = *src;
    des++;
    src++;
  }//注意没有将'\0'拷贝过去
  *des = *src;
  return ret;
}
int main()
{
  char arr1[] = "xxxxxxxxxxxxxx";
  char arr2[] = "abcdef";//注意'\0'也会拷贝过去
  char* ret =my_strcpy(arr1, arr2);//返回值是目的空间的起始地址
  printf("%s", ret);
  return 0;
}

接下来我们来一点点优化上面这个模拟strcpy的代码。

优化tip1

char* my_strcpy(char* des, char* src)//目的地址,源地址
{
  char* ret = des;
  while (*src != '\0')
  {
    *des++ = *src++;
  }//注意没有将'\0'拷贝过去
  *des = *src;
  return ret;
}
int main()
{
  char arr1[] = "xxxxxxxxxxxxxx";
  char arr2[] = "abcdef";//注意'\0'也会拷贝过去
  //返回值是目的空间的起始地址
  printf("%s", my_strcpy(arr1, arr2));//链式访问
  return 0;
}

后置++表现为两个效果,一个是原值与*结合之后,一个是地址往后移动一位。

优化tips2

#include<stdio.h>
char* my_strcpy(char* des, char* src)//目的地址,源地址
{
  char* ret = des;
  while (*des++ = *src++)
  {
    ;
  }
  return ret;
}
//这里des和src在跳出循环之前,往后走了一步,超出数组范围,
//但是在这里不影响,后面不用des src,但是要注意
int main()
{
  char arr1[] = "xxxxxxxxxxxxxx";
  char arr2[] = "abcdef";//注意'\0'也会拷贝过去
  //返回值是目的空间的起始地址
  printf("%s", my_strcpy(arr1, arr2));//链式访问
  return 0;
}

表达式的值

!!!特别提醒:++ = = =  不要弄混。

assert断言

如果传给des或src是空指针?为了以防万一

#include<stdio.h>
char* my_strcpy(char* des, char* src)//目的地址,源地址
{
  if (des == NULL || src == NULL)
  {
    return;
  }
  char* ret = des;
  while (*des++ = *src++)
  {
    ;
  }
  return ret;
}

但是无论是在Dug还是Release版本底下,若传入的指针为空,if语句都会执行。那能不能不执行,直接报错呢?当然可以。

#include<stdio.h>
#include<assert.h>//头文件
char* my_strcpy(char* des, char* src)//目的地址,源地址
{
  assert(des != NULL || src != NULL);
  assert(des  || src );
  //表达式为假就会报错
  char* ret = des;
  while (*des++ = *src++)
  {
    ;
  }
  return ret;
}
  • 包含头文件#include<assert.h>
  • 一旦括号里的表达式为假,那么会报错
  • 两种写法
  • 在Release版本底下会直接优化掉assert

const修饰

#include<stdio.h>
#include<assert.h>//头文件
char* my_strcpy(char* des, const char* src)//目的地址,源地址
{
  assert(des != NULL || src != NULL);
  //assert(des  || src );
  //表达式为假就会报错
  char* ret = des;
  while (*des++ = *src++)
  {
    ;
  }
  return ret;
}

const

const修饰变量

const修饰变量的时候,是在语法层面限制了const修改

但本质上,num还是变量,是一种不能被修改的变量

#include<stdio.h>
int main()
{
  int a = 10;
  int b = 0;
  printf("b=%d\n", b);
  b = a;
  printf("b=a=%d\n", b);
  return 0;
}
#include<stdio.h>
int main()
{
  int a = 10;
  const int b = 0;
  printf("b=%d\n", b);
  b = a;
  printf("b=a=%d\n", b);
  return 0;
}

虽然b被const修饰了,不能被改变了,那b还是变量吗?

#include<stdio.h>
int main()
{
  int a = 10;
  const int b = 0;
  int arr[b] = { 0 };
  printf("b=%d\n", b);
  //b = a;
  //printf("b=a=%d\n", b);
  return 0;
}

那如果找到b的地址,修改根据地址找到所指向空间里的值, 可以吗?居然可以。

const修饰指针

#include<stdio.h>
int main()
{
  int a = 10;
  const int b = 0;                                                 
  printf("b=%d\n", b);           
  int* p = &b;
  *p = a;
  printf("b=a=%d\n", b);
  return 0;
}

但是这样就打破了语法平衡,就像本来锁上大门不想让人进入,但是你偏偏要破窗而入。

所以我们需要制约,const修饰指针有两种形式:

  • const放在*的左边
  • const放在*的右边
const放在*左边
#include<stdio.h>
int main()
{
  int a = 10;
  const int b = 0;                                                 
  printf("b=%d\n", b);           
  const int* p = &b;
  *p = a;//err
  printf("b=a=%d\n", b);
  p = &a;//ok
  return 0;
}
const放在*右边
#include<stdio.h>
int main()
{
  int a = 10;
  const int b = 0;                                                 
  printf("b=%d\n", b);           
  int* const p = &b;
  *p = a;//ok
  printf("b=a=%d\n", b);
  p = &a;//err
  return 0;
}
//都不能修改
#include<stdio.h>
int main()
{
  int a = 10;
  const int b = 0;                                                 
  printf("b=%d\n", b);           
  const int* const p = &b;
  *p = a;//err
  printf("b=a=%d\n", b);
  p = &a;//err
  return 0;
}

综上四种情况:

不限制,限制*p,限制p,限制*p和p

  • const 放在*的左边:限制的指针指向的内容。也就是说:不能通过指针来修改指针指向的内容,但是指针变量是可以修改的,也就是指针指向其他变量的
  • const 放在*的右边:限制的是指针变量本身,指针变量不能再指向其他对象。但是可以通过指针变量来修改指向的内容。
  1. const如果放在*的左边,修饰的是指针指向的内容,保证指针指向的内容不能通过指针来改变。但是指针变量本身的内容可变。
  2. const如果放在*的右边,修饰的是指针变量本身,保证了指针变量的内容不能修改,但是指针指向的内容,可以通过指针改变。

注:《高质量C/C++编程》

示例2:模拟实现strlen函数

#include<stdio.h>
#include<assert.h>
size_t my_strlen(const char * arr)
{
  size_t ret = 0;
  assert(arr != NULL);
  //assert(arr);这样写也可
  while (*arr)
  {
    arr++;
    ret++;
  }
  return ret;
}
int main()
{
  char arr[] = "abcdef";
  size_t count = my_strlen(arr);
  printf("%Zd\n", count);
  return 0;
}

编程常见的错误

编译型错误

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

链接型错误

看错误提示信息,主要在代码中找到错误信息中的标识符,然后定位问题所在。一般是标识符名不存在或者拼写错误。C语言大小写敏感

int add(int a, int b)
{
  return a + b;
}
int main()
{
  int a = 10;
  int b = 20;
  int c = Add(a, b);
  printf("%d\n", c);
  return 0;
}
//Link 链接
//Link.exe

运行时错误

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

特别提醒,做一个有心人,积累排错经验,写成文章博客。以及笔记等等。(介绍每种错误怎么产生,出现之后如何解决)

✔✔✔✔✔最后,感谢大家的阅读,若有错误和不足,欢迎指正!下篇博文我们总结各种函数的模拟实现。每个人都要为自己所做的事情负责,所以不要拖延,不要害怕,不要在意任何的人目光,勇敢去做就好了,失败也没关系的77🙂

代码------→【gitee:唐棣棣 (TSQXG) - Gitee.com

联系------→【邮箱:2784139418@qq.com】

目录
相关文章
|
小程序 开发者 异构计算
小程序真机调试反应很慢卡顿,界面跳转之后,页面出现空白,无法点击等问题解决方案
小程序真机调试反应很慢卡顿,界面跳转之后,页面出现空白,无法点击等问题解决方案
1195 0
小程序真机调试反应很慢卡顿,界面跳转之后,页面出现空白,无法点击等问题解决方案
Idea单步调试快速跳过后面的断点-Mute Breakpoints 快速清空所有的断点
https://zhengyz.blog.csdn.net/article/details/128072266?spm=1001.2014.3001.5502
Idea单步调试快速跳过后面的断点-Mute Breakpoints 快速清空所有的断点
Pycharm主题切换(禁用)导致UI界面显示异常解决
问题记录 UI显示异常 安装多个主题时,当禁用某些主题,切换回one dark theme时,发现代码编辑窗口背景变成白色,菜单栏其他地方背景为黑色 问题原因 查看Settings>Editor>Color Scheme>General,发现方案被改为-Classic Light
|
8月前
|
程序员 C++ Windows
VS的调式技巧你真的掌握了吗?
VS的调式技巧你真的掌握了吗?
70 0
|
缓存 小程序 开发工具
解决微信开发工具的调试器加载错误,从任务栏打开工具可能导致该问题,请不要从任务栏启动工具。 和调试器控制台显示为空,刷新出不来的问题。
解决微信开发工具的调试器加载错误,从任务栏打开工具可能导致该问题,请不要从任务栏启动工具。 和调试器控制台显示为空,刷新出不来的问题。
647 0
|
NoSQL
vscode调式错误
vscode调式错误
159 0
|
C++
VS中断点调试的功能
VS中断点调试的功能
106 0
|
Web App开发 Apache PHP
phpstorm断点配置xdebuger
phpstorm断点配置xdebuger
145 0
phpstorm断点配置xdebuger
|
C++
C++程序调试详解(包括打断点 单步调试 数据断点...)
C++程序调试详解(包括打断点 单步调试 数据断点...)
380 0
C++程序调试详解(包括打断点 单步调试 数据断点...)