调试的定义及重要性
调试(Debug/Debugging),又称除错,是发现;和减少计算机程序或电子仪器设备中程序错误的一个过程。程序调试时将编译的程序投入实际运行前,用手工或编译程序等方法进行测试,修正语法错误和螺距错误的过程,是保证计算机信息系统正确性的必不可少的步骤。
调试的基本步骤
1.发现程序错误的存在 2.以隔离,消除等方式对错误进行定位 3.确定错误产生的原因
4.提出纠正错误的解决办法 5.对程序错误予以改正 再重新测试
Debug称为调试版本,它包含调试信息,并且不做任何优化,便于程序员调试程序。
Release称为发布版本,它往往是对代码进行了各种优化,使得程序在代码大小和运行速度上都是最优的,以便用户更好的使用。
最常用的几个快捷键:
F5:启动调试,经常用来直接跳到下一个断点处.F5一般要和F9配合使用 单独使用会直接显示调试结果 甚至会一闪而过.
F9:创建断点和取消断点.
断点的作用:可以在程序的任意位置设置断点。可以使得程序在想要的位置随意停止执行,继而一步步执行下去.也可以创建多个断点,设置条件断点或禁用断点.
F10:逐过程执行.
通常用来处理一个过程,一个过程可以是一次函数调用或者是一条语句.
F11:逐语句执行.
就是每次都执行一条语句,但是这个快捷键可以使我们的执行逻辑进入函数内部.
Ctrl + F5:开始执行不调试.
调试的时候查看程序当前信息
1.查看临时变量的值:在调试开始之后查看临时变量的值.
2.查看内存信息:在调试开始之后,用于观察内存信息.
3.查看调用堆栈:调用堆栈 右击显示外部代码 能反馈函数是如何调用的,通过调用堆栈,可以清晰的反应函数的调用关系以及当前调用所处的位置.
4.查看汇编信息:在调试开始之后,有两种方式转到汇编 . 第一种:右击鼠标,选择“转到反汇编”
第二种:调试 转到反汇编 可以切换到汇编代码
5.查看寄存器信息:可以查看当前运行环境寄存器的使用信息.
实例一:求1!+ 2!+ 3!…… +n!;不考虑溢出.
#include<stdio.h>
int main()
{
int n = 0;
scanf("%d", &n);
int i = 0;
int ret = 1;
int sum = 0;
int j = 0;
for (j = 1; j <= n; j++)
{
for (i = 1; i <= j; i++)
{
ret *= i;
}
sum += ret;
}
-
printf("%d\n", ret);
return 0;
程序输出结果:输入3,期待输出结果为3,但实际输出结果为15.
原因:上一次的ret的执行结果会影响到下一次的阶乘运算.
调试结果:将ret置为1 即int ret = 1
实例二:来源于《C陷阱与缺陷》的一道经典题目
#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("haha\n");
}
return 0;
}
如果是Debug模式去编译,程序的结果是死循环.
如果是Release模式取编译,程序没有死循环,运行结果为打印13个haha.
Debug模式调试结果:死循环的打印haha是因为arr[12]和 i ,共用一块地址空间,将arr[12]值改为0,i的结果也会跟着置为0.
原因:在Debug版本中 i的地址比arr的地址要大一些,在Realse版本中 i的地址比arr的地址要低一些,在Realse版本中 对代码进行了优化 使得内存开辟的方式与默认开辟的方式不同.
图示分析原因:
2016年Nice公司的校招笔试题目出自于此:
优秀的代码:
1.代码运行正常 2.bug很少 3.效率高 4.可读性高 5.可维护性高 6.注释清晰 7.文档清晰
常用的代码技巧:
1.尽量使用assert 2.尽量使用const 3.养成良好的代码风格 4.添加必要的注释 5.避免编码的陷阱
示范:模拟实现库函数:strcpy 和 strlen
#include<stdio.h>
模拟实现strlen
int my_strlen(const char* str)
{
int count = 0;
assert(str != NULL);
while(*str)
{
count++;
str++;
}
return count;
}
int main()
{
const char* p = "abcdef";
int len = my_strlen(p);
printf("len = %d\n", len);
return 0;
}
模拟实现strcpy
//char* strcpy(char* destination,const char* strsource)
//strcpy不用返回值来接收 strcpy在拷贝字符串的时候会把原字符串的\0也拷贝过去
代码1
void my_strcpy(char* dest, char* src)
{
while (*src != '\0')
{
*dest = *src;
dest++;
src++;
}
*dest = *src;
}
代码2
void my_strcpy(char* dest, char* src)
{
while (*src != '\0')
{
*dest++ = *src++;
}
*dest = *src;
}
代码3
void my_strcpy(char* dest, char* src)
{
while (*dest++=*src++)
{
;
}
}
代码4
#include<assert.h>
void my_strcpy(char* dest,const char* src)
{
断言 assert不止用于判断空指针 判断两个变量相不相等也可以
assert(src != NULL);
assert(dest != NULL);
while (*dest++ = *src++)
{
;
}
}
代码5:为了实现链式访问
char* my_strcpy(char* dest,const char* src)
{
strcpy函数返回的是目标空间的起始地址
char* start = dest
断言 assert不止用于判断空指针 判断两个变量相不相等也可以
assert(src != NULL);
assert(dest != NULL);
while (*dest++ = *src++)
{
;
}
return start;
}
int main()
{
char arr1[20] = { 0 };
char arr2[] = "hello world";
printf("%s\n", my_strcpy(arr1, arr2));
return 0;
}
int main()
{
char arr1[20] = { 0 };
char arr2[] = "hello world";
my_strcpy(arr1, arr2);
printf("%s\n", arr1);
return 0;
}
1.编译型错误:直接查看错误提示信息,解决问题.
2.链接型错误:看错误提示信息,主要在代码中找到错误信息的标识符,然后定位问题所在,一般是标识符名不存在或者拼写错误.
3.运行时错误:借助调试,逐步定位问题.
扫码关注腾讯云开发者
领取腾讯云代金券
Copyright © 2013 - 2025 Tencent Cloud. All Rights Reserved. 腾讯云 版权所有
深圳市腾讯计算机系统有限公司 ICP备案/许可证号:粤B2-20090059 深公网安备号 44030502008569
腾讯云计算(北京)有限责任公司 京ICP证150476号 | 京ICP备11018762号 | 京公网安备号11010802020287
Copyright © 2013 - 2025 Tencent Cloud.
All Rights Reserved. 腾讯云 版权所有