实用调试技巧
一、调试
1. bug是程序中存在的缺陷或错误,从而导致程序的运行结果不符合预期
2. 调试就是除错,是发现和减少程序错误的一个过程
3. Debug称为调试版本,包含调试的各种信息,通常所占内存空间比release版本大,不对代码进行优化,便于程序员调试程序
4. Release称为发布版本,对代码进行各种优化,在运行大小和速度方面都是最优的,便于用户使用
例:
#include <stdio.h>
int main()
{
int i=0;
int arr[10]={0};
for(i=0;i<=12;i++)
{
arr[i]=0;
printf("haha\n");
}
return 0;
}
● 如果在Debug版本下运行,程序会进入死循环,而在Release版本下则不会,这是因为Release版本对其进行了优化
● 为什么Debug版本下会陷入死循环?
1. 这是因为创建变量i需要开辟栈区空间,栈区空间内存使用习惯是先使用高地址,后使用低地址
2. 而数组随下标的增加,由低地址向高地址变化
3. 若数组与i之间存在适当空间,利用数组越界的操作则可能覆盖变量i的内存空间,这时变量i随越界的数组元素被赋值为0,一直满足i<=12,陷入死循环
图解如下:
二、Windows环境调试介绍
1. 调试需在Debug版本下进行
2. 一些快捷键的使用:
● F5:启动调试,通常用来直接跳到下一个断点处
● Ctrl+F5:开始执行不调试,让程序运行起来但不调试
● F9:创建和删除断点,可以在任意位置设置断点,使程序运行到设置的断点处暂停执行
● F10:在主函数内部逐过程调试,一个过程可以是一条语句,也可以是一次函数调用,但并不会进入调用函数的内部
● F11:从主函数开始逐语句调试,执行逻辑会进入调用函数的内部,这区别于F10
3. 调试时查看程序的当前信息
● 利用F10,F11或程序运行到断点处暂停执行时通过调试的窗口查看临时变量,内存,反汇编等相关信息
三、一些调试实例
例:模拟实现strcpy函数,将一个字符串的内容拷贝到另一个字符串中
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <assert.h>
char* my_strcpy(char* arr1,const char* arr2)//定义一个字符串拷贝函数,arr1是目标空间指针,arr2是源字符串指针
//const char* arr2避免了源字符串内容被改变
//返回值为目标空间的起始地址,是为了实现函数的链式访问
{
char* ret=arr1;
assert(arr1 != NULL && arr2 != NULL);//断言指针arr1和arr2不是空指针,若为空指针调试时会报错
while (*arr1++ = *arr2++)//*arr++表示先解引用指针arr,再将arr+1
//每将源字符串的一个字符拷贝到目标空间后,指针加一
//直到将源字符串的'\0'拷贝过去使表达式的结果为假,循环结束
{
;
}
return ret;
}
int main()
{
char arr1[] = "XXXXXXXXXXXXXXXXXXXXXX";
char arr2[] = "hello.";
my_strcpy(arr1, arr2);
printf("%s\n", arr1);
return 0;
}
例:const修饰指针变量
#include <stdio.h>
void test1()
{
int a = 10;
int b = 20;
int* p = &a;
*p = 60;
*p = &b;
}
void test2()
{
int a = 10;
int b = 20;
const int* p = &a;//const放在*左边,不能改变p所指向对象a的值,但能改变p的值
*p = 60;
p = &b;
}
void test3()
{
int a = 10;
int b = 20;
int* const p = &a;//const放在*右边,不能改变p的值,即不能改变它所指向的对象,但能改变所指向对象a的值
*p = 60;
p = &b;
}
系统提示错误:
由此可见,
const放在*左边,不能改变指针所指向对象的值,但能改变指针的值
const放在*右边,不能改变指针的值,但能改变指针所指向对象的值
四、编译常见错误
1. 编译型错误:编译器报错的错误,如语法错误一条语句结束未加分号等
2. 链接型错误:一般是指标识符错误,包括标识符名不存在或拼写错误。如调用函数时,该函数此前未定义或调用时函数名拼写错误
3. 运行时错误:程序正常运行,但结果不符合预期,需通过调试逐步定位问题,较难发现