实用调试技巧【下篇】

简介: 实用调试技巧【下篇】

3.2.调试的时候查看程序当前信息

3.2.1.查看临时变量的值

🔴自动窗口

👉调试–>窗口–>自动窗口

b7b5d166fc50452a922dd831541706b7.png

fa48f6133a5a40b1a366ed5b5aeddb77.png

它会出现一个自动窗口,它会自动捕获变量的值,让你监视,方便的我们不用手动添加变量,但是如果它不想让你看的时候过了一会就会自动取消了,我们就监视不到了

🔴局部变量

👉在自动窗口的下面

416eb93ff23043e8ac6327cb3aa96df9.png

与自动窗口比较相似,但不同的是放的是局部变量,监视的是程序执行中的局部变量

🔴监视

👉调试–>窗口–>监视

57b73e077eec41df84340096776ab73e.png这才是我们用到最多的调试功能

491b1afe45094d22bc3c5d6ec1862cac.png

我们想监视谁,就输入谁,它不会自己取消,会一直显示,来为我们提供对变量的监视

a729b36f3cd146de84327bed6ded2675.png

08bf3848b8044afbab75c276d72c4f62.png

数组也可以观察,变量的地址也都可以观察到

如果有需要,可以同时打开4个监视窗口,每个窗口都可以拖动,放到你想放的位置


3.2.2.查看内存信息

🔴内存

👉调试–>窗口–>内存

9b53fa7e08a942948a79a390494c112b.png

55057271aaa0434da3930690e9f844bd.png

☝️内存中本放的是二进制的数据,但是为了展示方便,所以是以十六进制来显示的☝️


3.2.3.查看调用堆栈

🔴调用堆栈

👉调试–>窗口–>调用堆栈

11d1b011aac148f88e6030157d474f57.png

👇简单知道一下 什么是栈👇

image.gif

👇看这段代码👇

edb400b346eb45f6851705a2788e29a7.png

👇调用堆栈👇

97192d6c15bc4c9c9a3340007fe47425.png

bc47a24c8fd54238bcdba5ee361b2f38.png

5b8d7b03f5bb486a82ded46152108fd0.png

79632eff51a04b74ab88d86bc64a957c.png

b548860ce8824e909f8f86876640e7bc.png

ad535fbcc62449a9afa3e07d68dc2fce.png

a16aeb5c6b84450c8e9bc13000b8b7d8.png

☝️反映的是函数的调用逻辑☝️

3.2.4.查看汇编信息

🔴反汇编

👉在内存的下面

89be58b6509d4f269467b8beb0ba9641.png

👇看到的是c语言代码翻译出来的汇编代码👇

5ada2955a9074c5ea3aeadc925b616ce.png


🥳4.调试实例

👇看下面这段代码👇

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

运行起来是什么样呢?

7fa427a29bc44493b9d7d7e494d0d70b.png

数组已经越界了,为什么没有崩溃而是死循环了呢?

👇进入调试👇

d92dd587d8a84ad381c843aea2179832.png

i 等于9的时候一切都正常,我们继续👇

8ba0f9bb748244dc8459593d83f60804.png

这个代码真的是在越界访问!胆大包天啊,如果我们继续下一步呢👇

3ae1a8be6e1a41c3a6435012974f00bf.png

继续一直执行430e1ab894f243b3925b64a8248c7550.png

再改

67c756a7bbca433d8afb8c77ba411a7e.png

i 到12之后并没有++变成13, i 再一直跟着 arr[12] 改变,陷入了死循环,这是为什么呢?有没有可能它们是在同一个空间呢?我们看一下👇

65faf90d58394d5b8c39e615b4efcb71.png

地址居然一摸一样,它俩在一个空间😕

那么底层原理什么呢?为什么会这样呢?👇

🔴原理:
1. i 和 arr 是局部变量,局部变量是放在栈区上的
2. 栈区内存的使用习惯是 使用高地址的空间,再使用低地址处的空间
3. 数组随着下标的增长,地址是由低到高变化的

ff4c575435854cef80fb04a02aa2f919.png

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

🥰优秀的代码:

🙌 1. 代码运行正常
🙌 2. bug很少
🙌 3. 效率高
🙌 4. 可读性高
🙌 5. 可维护性高
🙌 6. 注释清晰
🙌 7. 文档齐全

🥰常见的coding技巧:

🙌 1. 使用assert
🙌 2.尽量使用const
🙌 3. 养成良好的编码风格
🙌 4. 添加必要的注释
🙌 5. 避免编码的陷阱


5.1.模拟实现库函数: strcpy

3b23d1bdcf934babac2d2a72f9ebb3e5.png拷贝一个字符串

示例👇

#include<stdio.h>
#include<string.h>
int main()
{
  char arr1[] = "hello world";
  char arr2[20] = { 0 };
  strcpy(arr2, arr1);
  printf("%s\n", arr2);
  return 0;
}


3cad5d09e513439482a1bb29e10d9888.png

👇模拟实现 strcpy👇

#include<stdio.h>
#include<string.h>
void my_strcpy(char* dest, char* src)
{
  while (*src != 0)
  {
    *dest = *src;
    dest++;
    src++;
  }
  *dest = *src; // \0的拷贝
}
int main()
{
  char arr1[] = "hello world";
  char arr2[20] = { 0 };
  my_strcpy(arr2, arr1);
  printf("%s\n", arr2);
  return 0;
}

如果写成这样的代码,这算一个好的代码吗?当然不算一个好的代码,我们可以优化一下👇

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

我们还可以再融合一下👇

void my_strcpy(char* dest, char* src)
{
  while (*dest++ = *src++)
  {
    ;
  }
}

可以直接一次搞定

这样改进完好像已经足够好了,但是我们还能再改进👇

void my_strcpy(char* dest, char* src)
{
  if (dest == NULL || src == NULL)
  {
    return;
  }
  while (*dest++ = *src++)
  {
    ;
  }
}

防止遇到空指针,但是这样处理遇到问题只是回避掉了,并不做处理👇

#include<stdio.h>
#include<string.h>
#include<assert.h>
void my_strcpy(char* dest, char* src)
{
  //断言
  assert(dest != NULL);
  assert(src != NULL);
  while (*dest++ = *src++)
  {
    ;
  }
}
int main()
{
  char arr1[] = "hello world";
  char arr2[20] = { 0 };
  my_strcpy(arr2, NULL);
  printf("%s\n", arr2);
  return 0;
} 

29a4a9a1cfd74e3ba646766a0c09bc94.png

使用断言处理之后,如果有问题它就会报错,把问题抛出来并且告诉你出错在哪里,如果不使用断言执行程序的话代码就崩掉了,不会告诉你哪里有问题👇

69096e9b8fb949eda28406f8063c4b87.png

🚨断言是对程序员非常友好的东西,我们使用断言是个很好的编程习惯

🚨使用断言别忘记引用头文件 <assert.h>

void my_strcpy(char* dest, char* src)
{
  //断言
  assert(dest && src);
  while (*dest++ = *src++)
  {
    ;
  }
}

简便一点可以直接写成这样☝️

5.2. const 修饰指针

fb75667943e34614bc27cde02adc124a.png

const修饰变量m之后,m的值就更改不了了

但是可以使用一些小聪明把m改掉👇

int main()
{
  const int m = 10;
  //m = 20;//error
  int* p = &m;
  *p = 20;
  printf("%d\n", *p);
  return 0;
}

faaa08cf19db421f89a1b9ae1dd7321d.png

这个代码非常奇葩,因为 const 只是在语法层面限制了m不能改,但是通过地址是可以更改的

我们可以在语法层面把指针p 也进行限制👇

23599851c67f40bb83dbd590282e579f.png

int main()
 {
  int n = 100;
  const int m = 10;
  //m = 20;//error
  //const 修饰指针
  //1. const放在*的左边,*p不能改了,也就是p指向的内容,不能通过p来改变了。但是p是可以改变的,p可以指向其他变量
  const int* p = &m;
  p = &n;
  printf("%d\n", *p);
  return 0;
 } 

3489c890607947b7a5abe664ccc30ea3.png

int main()
 {
  int n = 100;
  const int m = 10;
  //m = 20;//error
  //const 修饰指针
  //2. const放在*的右边,限制的是p,p不能改变,但是p指向的内容*p,是可以通过p来改变的
  int* const p = &m;
  *p = n;
  printf("%d\n", *p);
  return 0;
 } 

d82b9c3989d64584929765201e5561c7.png

🤜 1. const放在的左边,p不能改了,也就是p指向的内容,不能通过p来改变了。但是p是可以改变的,p可以指向其他变量

🤜2. const放在的右边,限制的是p,p不能改变,但是p指向的内容p,是可以通过p来改变的

void my_strcpy(char* dest,const char* src)
{
  //断言
  assert(dest && src);
  while (*dest++ = *src++)
  {
    ;
  }
}

所以上面模拟实现strcpy的代码这样写就更严谨了,加上了 const 来修饰char* src

🥰提高了代码的健壮性(鲁棒性)

因为如果下面的 *dest++ = *src++ 位置要是不小心写反了程序就会报错,所以这样写才是有意义的!

我们再来进行最后一次优化👇

#include<stdio.h>
#include<string.h>
#include<assert.h>
//strcpy函数返回的是目标空间的起始位置
char* my_strcpy(char* dest,const char* src)
{
  //断言-- 保证指针的有效性
  assert(dest && src);
  char* ret = dest;
  //把src指向的字符串拷贝到dest指向的数组空间,包括\0字符
  while (*dest++ = *src++)
  {
    ;
  }
  return ret;
}
int main()
{
  char arr1[] = "hello world";
  char arr2[20] = { 0 };
  //链式访问
  printf("%s\n", my_strcpy(arr2, arr1));
  return 0;
} 

🥰这些都是一些技巧,希望大家可以理解🥰

🥳6.编程常见的错误

🥰优秀的代码:

🔴1. 编译型错误

👉直接看错误提示信息 解决问题,或者凭借经验就可以搞定,相对简单

🔴2. 链接型错误

👉看错误提示信息,主要在代码中找到错误信息中的标识符,然后定位问题所在。一般是标识符名不存在或者拼写错误(ctrl + F 可以进行搜索)

🔴3. 运行时错误

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

🚨做个用心的人!积累排错经验!

总结🥰
本文章是在 Visual Studio 2022(VS2022)编译环境下进行操作讲解
以上就是调试技巧下篇内容啦🥳🥳🥳🥳

希望我们可以做一个用心的人💕💕💕

小的会继续学习,继续努力带来更好的作品😊😊😊

创作写文不易,还多请各位大佬uu们多多支持哦🥰🥰🥰

f09da3b0dc2f4da1a8ec977430b265f0.gif

目录
相关文章
|
5月前
|
Shell 数据安全/隐私保护 开发者
详细解读ApolloGPS调试笔记
详细解读ApolloGPS调试笔记
23 0
|
6月前
|
JSON 程序员 API
现在的程序员真是越来越懒了,api 文档都懒得写!程序员:“api工具惯的”
现在的程序员真是越来越懒了,api 文档都懒得写!程序员:“api工具惯的”
|
6月前
|
机器学习/深度学习 图计算 异构计算
|
程序员 编译器 C++
适合初学者的超详细实用调试技巧(上)一
适合初学者的超详细实用调试技巧
85 0
|
安全 程序员
适合初学者的超详细实用调试技巧(下)二
适合初学者的超详细实用调试技巧
46 0
|
程序员 Windows
适合初学者的超详细实用调试技巧(上)二
适合初学者的超详细实用调试技巧
71 0
|
编译器
适合初学者的超详细实用调试技巧(下)一
适合初学者的超详细实用调试技巧
71 0
|
程序员
实用调试技巧【上篇】
实用调试技巧【上篇】
102 0
|
程序员 编译器 C语言
程序员必备的VS调试技巧
程序员必备的VS调试技巧
159 0
程序员必备的VS调试技巧
|
存储 NoSQL Ubuntu
调试基础知识及原理
调试基础知识及原理
124 0