c语言|C语言—实用的调试技巧

每天进步一点点,坚持带来大改变,下面我们开始一个新的征程学习:

目录
1.什么是调试?
2.如何调试?
3.Debug和Release
4.windows环境下的调试:
1.调试环境准备:
2.调试快捷键的介绍
3.调试的时候程序当前的值:
1. 查看临时变量的值:
2.查看临时变量的地址:
5.调试实例:
7.如何写出优秀的代码?
1.优秀的代码:
2.常用的代码技巧:
3.示范:
assert:
const :


前言:当我们写代码想实现某个功能的时候,代码实现的功能却不尽如意,这个时候如果我们检查语法没有问题的话,可能就是程序段逻辑上出现了问题,而这个问题就称为“bug”,接下来我们详细了解一下如何调试找出“bug”并解决"bug"。
1.什么是调试?
调试:找出程序段出现问题的过程被称为调试。
2.如何调试?
发现程序错误的存在
以隔离、消除等方式对错误进行定位
确定错误产生的原因
提出纠正错误的解决办法
对程序错误予以改正,重新测试
3.Debug和Release
什么是Debug?
Debug:被称为调试版本,不做任何优化。
什么是Release?
Release:被称为发布版本,程序段在编译的过程中编译器自动进行代码的优化。

Debug和Release有什么区别?
1. Debug版本下,程序员可以自行调试代码,Relase版本下程序员不能自己调试代码。
2.Debug版本和Release版本在编译形成的可执行程序所占内存大小不同。
Debug:
c语言|C语言—实用的调试技巧
文章图片

Release:
c语言|C语言—实用的调试技巧
文章图片


4.windows环境下的调试: 1.调试环境准备:
调试:在Debug版本下进行。
2.调试快捷键的介绍
F5:开始调试
F9:创建断点和取消断点。
作用:可以让代码在任何打断点的位置停下来。
c语言|C语言—实用的调试技巧
文章图片


F10:逐过程,通常用来处理一个过程,过程可以是一次函数调用或者是一条语句。
F11:逐语句,每次执行一条语句,通常当我们向进入函数内部的时候用这个键进行调试。
Ctrl+F5:代码直接运行补调试。
注:如果你的电脑有Fn键的时候上面的所有快捷键都要加上Fn键。
3.调试的时候程序当前的值: 1. 查看临时变量的值:
int Add(int x, int y) { return x + y; } int main() { int a = 10; int b = 20; int ret = Add(a, b); printf("%d\n", ret); return 0; }

找到监视:
c语言|C语言—实用的调试技巧
文章图片



查看上面代码段中临时变量a,b,x,y的值:
c语言|C语言—实用的调试技巧
文章图片


2.查看临时变量的地址:
#includeint Add(int x, int y) { return x + y; } int main() { int a = 10; int b = 20; int ret = Add(a, b); printf("%d\n", ret); return 0; }


找到内存:
c语言|C语言—实用的调试技巧
文章图片

查看上面代码段中临时变量a,b,x,y的地址:

c语言|C语言—实用的调试技巧
文章图片

c语言|C语言—实用的调试技巧
文章图片

c语言|C语言—实用的调试技巧
文章图片

c语言|C语言—实用的调试技巧
文章图片


5.调试实例: 例1:
求n的阶乘:
思路:求出出每个数的阶乘进行累加

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

c语言|C语言—实用的调试技巧
文章图片

当我们求3的阶乘的时候,本来应该为9,为何等于15呢?
说明我们的程序段中逻辑出现了问题,下面我们用调试的方法寻找问题并解决问题。
进行调试:c语言|C语言—实用的调试技巧
文章图片

c语言|C语言—实用的调试技巧
文章图片

调试的过程中发现计算3的阶乘时:
本应该是:3!=3*2*1=6
实际上:当i==1 时ret==1
i==2j==1ret==1
j==2ret==2
i==3j==1ret==2
j==2ret==4
j==3ret=12
通过调试分析:发现ret的值每次在原来的基础上进行乘,原来问题出现在ret,因此只需在计算某个数的阶乘的时候将ret置为1就可以了。
调试后的正确代码:
#includeint main() { int n = 0; scanf("%d", &n); int i = 0; int j = 0; int ret = 1; int sum = 0; for (i = 1; i <= n; i++) { ret = 1; for (j = 1; j <= i; j++) { ret *= j; } sum += ret; } printf("%d\n", sum); return 0; }

c语言|C语言—实用的调试技巧
文章图片


例2:
#includeint main() { int i = 0; int arr[10] = { 0 }; for (i = 0; i <= 12; i++) { arr[i] = 0; printf("hehe\n"); } return 0; }

c语言|C语言—实用的调试技巧
文章图片

运行的时候代码陷入了死循环。
这是为什么呢?
下面通过调试的方式来分析一下原因:
调试代码:c语言|C语言—实用的调试技巧
文章图片

c语言|C语言—实用的调试技巧
文章图片

c语言|C语言—实用的调试技巧
文章图片

通过调试发现代码陷入死循环的原因是因为i的地址和arr[12]的地址相同。改变arr[12]的时候i的值也被改变,i的值永远<=12。
详解死循环原因:
通过底层内存分布来解释:
c语言|C语言—实用的调试技巧
文章图片


7.如何写出优秀的代码? 1.优秀的代码:
1. 代码运行正常
2. bug很少
3. 效率高
4. 可读性高
5. 可维护性高
6. 注释清晰
7. 文档齐全
2.常用的代码技巧:
1. 使用assert
2. 尽量使用const
3. 养成良好的编码风格
4. 添加必要的注释
5. 避免编码的陷阱。
3.示范: 1.库函数:strcpy:字符串拷贝
注意:
1.包含头文件
2.将'\0'也拷贝过去的时候结束
#include #include int main() { char ch1[20] = { 0 }; char ch2[] = "hello world"; //将ch2中的字符串拷贝到ch1 strcpy(ch1, ch2); printf("%s\n", ch1); return 0; }

c语言|C语言—实用的调试技巧
文章图片


2.模拟实现stcpy函数
初级代码:
#includevoid my_strcpy(char* dest, char* src) { //当src指向'\0'时循环结束 while (*src != '\0') { *dest = *src; //拷贝 dest++; //拷贝完之后指向下一个字符 src++; } *dest = *src; //将'\0'也拷贝过去 } int main() { char arr1[20] = { 0 }; char arr2[] = "hello world"; //将arr2中的字符串拷贝到arr1 my_strcpy(arr1, arr2); printf("%s\n", arr1); return 0; }

问题:代码不够简洁,并且容易出现问题
中级代码:
#include #include//使用assert包含头文件 void my_strcpy(char* dest, char* src) { //断言:当传参传过来空指针的时候会报错 //如果空指针不报错,对空指针进行解引用就会形成野指针的问题 assert(dest != NULL); assert(src != NULL); //拷贝完之后++指向下一个字符,当'\0'也拷贝过去的时候循环也结束了。 while (*dest++ = *src++) { ; } } int main() { char arr1[20] = { 0 }; char arr2[] = "hello world"; my_strcpy(arr1, arr2); printf("%s\n", arr1); return 0; }

解决了代码出现问题,和不够简洁的问题
高级代码:
#include #include //为什么返回char*呢? //是为了实现函数的链式访问,返回值可以作为其它函数的返回值 //strcpy函数返回的是目标空间的起始位置 //用const修饰时防止源字符串被改 char* my_strcpy(char* dest, const char* src) { //为了返回起始位置,因为dest已经被改了 char* ret = dest; assert(dest != NULL); assert(src != NULL); while (*dest++ = *src++) { ; } return ret; } int main() { char arr1[20] = { 0 }; char arr2[] = "hello world"; printf("%s\n", my_strcpy(arr1, arr2)); return 0; }

优化了源字符串被改的问题和实现了函数的链式访问。
assert:
assert:断言,当指针为空指针的时候会报错。
作用:防止野指针的问题
注:头文件
c语言|C语言—实用的调试技巧
文章图片



c语言|C语言—实用的调试技巧
文章图片

const :
const:如果我们不想让一个变量值发生改变的时候用const修饰
const修饰变量
c语言|C语言—实用的调试技巧
文章图片
a不能再被修改了
const修饰指针变量:
代码1:
int main() { const int a = 10; const int* pa = &a; //const int *pa等价于int const *pa *pa = 20; //err return 0; }

int main() { const int a = 10; const int* pa = &a; int b = 20; pa = &b; //ok return 0; }

【c语言|C语言—实用的调试技巧】
const放在*的左边:pa指向的对象不能通过pa来改变了,但是pa变量本身的值可以改变
代码2:
int main() { const int a = 10; int* const pa = &a; *pa = 20; //ok return 0; }

int main() { const int a = 10; int* const pa = &a; int b = 20; pa = &b; //err return 0; }


const放在*的右边:pa变量指向的对象可以通过pa来改变,pa变量本身的值不能改变
总结:以上是对如何遇到问题调式代码和如何写出优秀的代码的一个小结,希望能对你有所帮助!
最后送给大家一句话:
每一段努力的过程都是值得被尊重的,没有天生完美的人生,只有努力过越来越好的生活。

    推荐阅读