Vc6.0 调试指南 --Happy Debugging
Vc6.0 调试指南 --Happy Debugging回去洗个澡,再来完成这项浩大的工程。
//Vc6.0下载 https://down.bccn.net/100.html
//Visual AssistX //Vc6.0的一个好用的插件 https://down.bccn.net/361.html
//如果您 Vc6.0 还不知道怎么使用, 请点击 https://down.bccn.net/357.html 下载 Pdf 文档 自行学习。
/*****************************************
一:深入了解 编译、链接、组建(Look into Compile、Linking、Build)
(1) Compile
(2) Linking
(3) Build
二:断点 (Break Point)
(1) 普通断点 (Nomal Break Point)
(2) 条件断点 (Condition Break Point)
(3) 数据断点 (Data Break Point)
三:断点之后能做些什么?(What can I do after Break Point)
(1)variables
(2)watch
(3)stack
(4)memory
四:断言 (Assert)
五:printf()
六:Log
七:Trace
八:虚拟内存简介(Virtual Memory Intro )
九:常见的段错误 (Common Segmentation Fault)
(1)堆区内存错误 (Heap Memory Errors)
1,未初始化的内存仿问 (Uninitialized Memory Access)
2,无效的内存仿问 (Invalid Memory Access)
3,内存泄露 (Memory leaks)
4,未分配内存 (Missing allocation)
(2)栈区内存错误 (Stack Memory Errors)
1,未初始化的内存仿问 (Uninitialized Memory Access)
2,无效的内存仿问 (Invalid Memory Access)
3,数组越界 (Writing off the end of the array)
4,栈溢出 (Stack Overflows)
十:轻松解决 内存泄漏 (Hunting Memory Leaks)
结束语:怎样尽可能的避免错误
**********************************************************/
下面就开始祥细讲解,/
一:深入了解 Compile、Linking、Build
(1)Compile - 编译
当您点击 编译按钮时,编译器将会把你的源代码文件 (.c文件)转换为目标文件(.obj文件) ,目标文件包含的是
源代码文件翻译后的机器语言。这些是不能被直接运行的,还需要 链接器将此中间代码与其他代码相结合来生成可执行文件。请转看 Linking,
Compile时,编译器通常会给你2种类型的提示:warnings 和 errors
warnings
别小看 warnings ,它有可能会导致相当严重且极其隐蔽的 bug,尤其是在 指针管理内存 这一块,/
常见的warning有以下几种类型
1,使用了未经初始化的变量,或者定义变量了却没有使用。
解析: 未经初始化的变量会 存一个随机值,绝大多数的时候这个值都不是你想要的,你用它,编译器能不给你warning吗,?
2,使用了一些看上去非常愚蠢的语句,编译器都看不下去了
例如, if (blueguy = 0)
printf("blueguy = 0!!");
if(blueguy && greengirl || hemy)
;
3, 使用了未定义的语句 (注意,vc6.0是不会给这样的语句一个warning的)
例如, j = i++ + i++; //我自己都不知道自己想表达什么意思 , 呵呵
x = x>0 ? x++ : x--;
4,类型不匹配
例如, char * blueguy = (int*) greengirl;
本意是按单字节仿问内存的,结果却按四字节仿问内存, 你感到崩溃,我感到崩溃,编译器也感到崩溃,估计编译器会真的崩溃了 ,/
5, 函数原型明明写着有返回值的,结果函数体内却没有 return一个值, 反之亦然。
例如,
int main(void)
{
}
或者
void main()
{
return 0;
}
......等等,等等,等等。/
好了,warnings 就简单介绍到这里了,希望您写的程序里 一个 warning也没有
errors
出现errors时,相对来说比较好解决一些, 通常编译器会给你明确的提示
像,"syntax errors", "unexpected parenthesis ", "unexpected end of file"之类的,
常见的errors有以下几种类型
(1)语句缺少 ";"号
例如,
for(;)
struct bluguy
{
int x;
}
(2)括号不匹配
例如,
int main(void)
{
if (!blueguy
Compile就这样结束了,下面接着看 Linking
(2)Linking - 链接
vc6.0上是没有 Linking按钮的,或许是 我菜了,/ 没注意到
vc6.0的 Build 把 Compile与Linking合在一起了 ,/
链接的作用是将目标代码、系统的标准启动代码和库代码结合在一起,生成可执行程序。
在你Compile的时候,编译器假定所有的结构体、函数、全局变量都已经在别的文件里声明了,但这个假设并不总是成立的,
链接器就是在文件中查看这些结构体、函数以及全局变量是不是已经声明了,/
常见的Linker Errors有以下几种,
1, "undefined function" - 不明确的函数 (这可能是函数参数不匹配 或者未包含相应的库 或者函数没有函数原型造成的,/)
2,"could not find definition for X" - 使用的变量未定义,
3,"multiple definitions" - 多重定义(多个文件定义了相同的函数或全局变量)
(3)Build - 组建
Build没什么好讲的,就是集成了Compile与Linking 的功能。
将Compile与Linking 分隔开来 ,可以让你能够单独编译文件,总之是为了方便管理的,/
顺便说一下,当您的程序出现了,warning 或者 Errors 的时候,双击一下提示信息就可以定位到那一行,/
二:断点
(1) 普通断点,
图1
nomal.jpg (60.42 KB)
普通断点是最简单, 也是最常用的,只要在能够下断点的位置下上断点(按下F9,有的行是不能下断点的),上图中,断点下在 blueguy = 0;这条语句上,也就是第9行,以下简称"第9行",好,按下 F5, 程序立马就断在 blueguy = 0; 这条语句上。断下来有什么用?请跳转本文列表三:断点之后能做些什么?
(2) 条件断点1 - 遇到断点一定次数后断下来
还是在图1的第9行下个断点,然后,按下 Ctrl+b, 弹出如下对话框
图2
condition1.jpg (32.56 KB)
单击,at{"blueguy.c"}.9 这一行后,弹出如下对话框
图3
condition2.jpg (39.05 KB)
好,现在单击 Condition按钮,弹出如下对话框
图4
condition3.jpg (50.53 KB)
接下来, 在stopping标识的文本框内填上一个你想要填上的数字,这里填的是 6。好,
单击 OK按钮 ,再次按下 F5, 程序断在了第9行。此时按下 Ctrl+b 可以看到
图5
condition4.jpg (34.93 KB)
(2) 条件断点2 - 某个变量(普通变量或指针变量)的值发生变化时断下来
还是在第9行下上断点, 先按下F5,程序断在了第9行,按下 Ctrl+b
弹出如下对话框
condition2.jpg (39.05 KB)
再按下 Condition按钮,弹出如下对话框
condition3.jpg (36.24 KB)
在Enter the expression to be evaluated标识的文本框下
写上你想写入的变量,这里写的是 blueguy, 再按下 F5, 好,程序断在了第9行,并伴有下图提示
condition5.jpg (24.97 KB)
(3)数据断点 - 某个表达式的值为真时断下来
还是在第9行下上断点, 先按下F5,程序断在了第9行,按下 Ctrl+b
condition2.jpg (39.05 KB)
单击下Date按钮,弹出如下对话框,
data1.jpg (41.11 KB)
在Enter the expression to be evaluated标识的文本框下
写上你想写的表达式,这里写的是 greengirl == 6
好,先按下F9撤消断点,再按下F5, 程序断在了第9行,伴有下图提示:
见下楼,
data2.jpg (33.24 KB)
如果你嫌麻烦的话,你也可以这样下断点
if (greengirl == 6)
blueguy = 0;
不过这样做,调试过后你得记得删除它,否则会给阅读代码选成 视觉障碍,
好了,怎样下断点就讲到这里,如果你细心的话,会发现还有个 Message按钮, 那是消息断点,不怎么常用,
不在我的讲解之列 ,/
三:断点之后能做些什么?
先上张大图
debug.jpg (146.13 KB)
假设程序断在了第9行
(1)variables
这个窗口可以查看 自动变量的值,不过他的最大用途在于查看函数的返回值。
假设 有这样一个程序段
#include <stdio.h>
int blueguy(void){return 5;}
int sum(int a, int b){return a+b;}
int main(void)
{
printf("%d", sum(5, blueguy()));
return 0;
}
如果,我想查看 sum() 以及 blueguy()的返回值,如果不使用 variables窗口,
我就得 定义两个变量来接收 sum() 以及 blueguy()的返回值,例似这样的语句,int greengirl = blueugy().... 是不是? 很幸运, variables窗口为我们省去了这个麻烦。
现在我们把断点下在 return 0; 按下 F9, 程序断下来。
看下面这张图, 很惊喜吧,
Autos.jpg (59.43 KB)
(2)watch
点击调试工具条上的 watch按钮
1,现在想查看下greengirl的值,只需把光标放在 greengirl上选中,拖到 watch窗口里就行了
2,watch窗口中,在整形变量后面加上",c"可以显示该变量对应的ASCII字符,也可以直接敲数字这么显示,比如118,c的对应值是'v', 'v',d就是显示字符'v'对应的十进制ASCII码值 是118, 'v',x显示的是对应的十六进制的ASCII码值
3, 数组名后加上",N" (N表示表示数组元素个数) 可以查看该数组的值, 对于查看大型数组的尾部数据比较方便
例如, int blueguy[100][10] = {...};
watch窗口中, 输入blueguy[99], 10, 可以看到二维数组最后一维的数据。
4, watch可以计算简单算式的值,例如: blueguy - greengirl
(3)stack
点击调试工具条上的 stack按钮
假设现在内存崩了,且崩在了第9行,stack 显示了函数的调用关系, 可以逐级向上检查错误
(4)memory
点击调试工具条上的 memory按钮
memory窗口可以查看指针所指的内存区域里的值,使用时,把指针的值放在Address里就可以了,这在查看声音、图片、文件等大型数组的值时相当方便
(5)disassembly
有时内存崩了,弹出这个反汇编窗口,在这个窗口里可以看到相应的函数,不过用途不大。
还有两个不常用的窗口, 不在我的讲解之列
断点之后能做些什么?就简单的介绍到这里了,反正这些都是很常用的,具体怎么用不在我的讲解之列
四:断言
使用断言时,先加上 头文件, <assert.h>
断言是在条件表达式不成立的情况下,终止程序。
断言是用来调错的,不要用来作为异常处理语句
假设 现在内存崩了,并且程序中有如下代码
char *blueguy = malloc(10000);
我现在想看看 是不是blueguy分配失败了,
可以这样写个 断言
char* blueguy = malloc(blueguy);
assert(blueguy);
现在假设 blueguy 分配失败,为空,那么编译器就会弹出类似下图的提示框
assert.jpg (32.26 KB)
这个窗口上面写着 错误在哪一个文件,哪一行 ,此时找到那个文件,按下 ctrl+g,在框内填上行数就可以定位到那一行。/
五: printf()
现在讲解下深受控制台下编程的朋友钟爱的printf()函数; 但请注意,不要迷恋 printf();因为他的功能并不是那么强大,想查看某个值的时候,断个点就可以,不用花力气去书写 printf()输出语句。
不过printf()也有它的优点,想查看变量之外的信息的时候,还是很有用的
比如说,我想看下 汉诺塔 递归程序的 函数调用路径
可以 定义一个 监视变量 表示递归深度 ,/
Example:
//声明:本人不保证程序能够正确运行,
#include <stdio.h>
void Hanoi(int n, char a, char b, char c)
{
if ( n == 1 )
{
while(n--)
printf(" ");
printf("Hanoi(%d, %c, %c, %c)\n", n, a, b, c);
//cout << a << "->" << b << endl;
//printf("%c -> %c\n", a, b);
return ;
}
while(n--)
printf(" ");
printf("Hanoi(%d, %c, %c, %c)\n", n, a, b, c);
//先将n-1个盘子,以b为中转,从a柱移动到c
Hanoi( n-1,a,c,b);
//将一个盘子从a移动到b
//cout << a << "->" << b << endl;
//printf("%c -> %c\n", a, b);
//将c柱上的n-1个盘子,以a为中转,移动到b柱
Hanoi( n-1,c,b,a);
}
int main(void)
{
int N;
scanf("%d", &N);
Hanoi(N,'A','B','C');
return 0;
}
当然,printf()还有其他的用途,关键看你怎么用了,不在我的讲解之列
六:Log
有时候,前面介绍的调试技术(本文已经通过 编程中国ISO9001认证)并不能直观的看到数据的变化,比如 watch窗口太小了,放不下那么多的值...
Log是java/c#中的名词,c 语言是没有提供这个调试功能的,实际上所谓的Log也没什么东西,无非是打开文件,写入数据罢了。
我们可以用 c语言 的 fopen() 、fwrite ()等文件操作函数来仿写 Log
这里要介绍两个用于调试的重要的宏
__FILE__ 表示 文件名
__LINE__ 表示调用语句所在的行数
Example:
//声明:本人不保证程序能够正确运行,
void Log(void)
{
char bugInfo[200];
pFile = fopen ("E:\\blueguyDebug.log", , "a+" );
sprintf(bugInfo, "File: %s Line: %d: Here is a bug", __FILE__, __LINE__);
fwrite (bugInfo, sizeof(bugInfo[0]) , sizeof(bugInfo) , pFile );
fclose (pFile);
}
/*一套完整的日志接口*/
void bgOpenLog(void)
{
if(Log == NULL)
Log = fopen("log.txt", "w");
}
void bgWriteLog(const char* s)
{
fwrite(s, sizeof(char), strlen(s), Log);
}
void bgCloseLog(void)
{
fclose(Log);
Log = NULL;
}
不再多说了,继续看下一个调试技术
七:Trace
假设你现在 在写windows应用程序, 程序出错了,你想用printf()函数来输出信息, 很遗憾的告诉你,可能没有,
这个时候,您可以使用 可变参数来仿写 printf()函数
Example:
//声明:本人不保证程序能够正确运行,
Trace(const char* fmt,...)
{
char text[256];
va_list ap;
if (fmt == NULL)
return;
va_start(ap, fmt);
vsprintf(text, fmt, ap);
va_end(ap);
//在屏幕上画出这个字符串
}
八:虚拟内存简介 (Virtual Memory)
每一个跑在你的操作系统的应用程序都有一个唯一的地址空间。这些地址空间看起来是连续的内存块,实事上,它们并不是物理上连续的内存,仅仅是操作系统给应用程序的一个镜像空间--虚拟内存.
每个应用程序可利用的虚拟内存一般划分为六个段:
环境变量段 -- 用于存储环境变量和命令行参数。
栈 -- 用于存储函数参数,自动变量等。
堆 -- 用于动态分配内存
两个数据段 -- 分别用于存储 初始化和未初始化的 静态变量或全局变量
文本段 -- 用于存放实际的代码
九:常见的段错误 (Common Segmentation Fault)
(1)堆区内存错误 (Heap Memory Errors)
1,未初始化的内存仿问 (Uninitialized Memory Access)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(void)
{
char *pStr = (char*) malloc(512);
char c = pStr[0]; // the contents of pStr were not initialized
}
2,无效的内存仿问 (Invalid Memory Access)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(void)
{
char *pStr = (char*) malloc(25);
free(pStr);
strcpy(pStr, "blueguy")// Invalid write to deallocated memory in heap
}
3,内存泄露 (Memory leaks)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(void)
{
char *pStr = (char*) malloc(512);
return;
}
4,未分配内存 (Missing allocation)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(void)
{
char* pStr = (char*) malloc(20);
free(pStr);
free(pStr); // results in an invalid deallocation
}
(2)栈区内存错误 (Stack Memory Errors)
1, 无效的内存仿问 (Invalid Memory Access)
#include <stdio.h>
int* blueguy(void);
int main(void)
{
int *greengirl = NULL;
greengirl = blueguy();
printf("%d", greengirl[0]);
return 0;
}
int* blueguy(void)
{
int a[10] = {1,2,3,4,5,6,7,8,9,10};
return a;
}
2,未初始化的内存仿问 (Uninitialized Memory Access)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(void)
{
int a;
int b = a * 4; // uninitialized read of variable a
}
3,数组越界 (Writing off the end of the array)
//
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(void)
{
int blueguy[10];
printf("%d", blueguy[10]);
return 0;
}
4,栈溢出 (Stack Overflows)
//输入了小于0的值
#include <stdio.h>
int factorial(int n);
int main(void)
{
int blueugy, greengirl ;
scanf("%d", &blueguy);
greengirl = factorial(blueguy);
printf("%d", greengirl);
return 0;
}
int factorial(int n)
{
if(n == 0)
{
return 1;
}
return factorial(n-1) * n;
}
//超过栈内存分配限制
#include <stdio.h>
int factorial(int n);
int main(void)
{
int blueugy[1000000];
return 0;
}
十:轻松解决 内存泄漏(Hunting Memory Leaks)
为了解释方便,假设现在程序需要载入500张图片。
给图片结构体 定义一个 id;
typedef struct bgImage
{
int id;
char* buffer;
int width;
int height;
}BGImage;
typedef BGImage* Image;
定义一个 int inspect[500] (初始化为 0)数组来监视内存。
给每个文件名编号,从100开始,(之所以不从0开始编号,是为了处理方便),然后101,...
(1)
每次载入图片的时候,根据路径名计算出 图片id。
bgMalloc()
{
ID = (path[0]-'0')*100 + (path[1]-'0')*10 + (path[2]-'0');
img->id = ID;
inspect[img->id - 100]++;
for (i = 0; i < 500; i++)
{
if (inspect[i] > 1)
printf("内存泄漏");
}
(2)
每次释放图片的时候
bgFree(Image img)
{
inspect[img->id - 100]--;
free(img->buffer);
img->buffer = NULL;
free(img);
img = NULL;
}
(3)
在某个时刻,根据 监视列表 判断内存是否泄漏。比如程序退出的时候。
for (i = 0; i < 500; i++)
{
if (inspect[i] > 0)
printf("内存泄漏");
}
小帖士:
(1)定义指针变量时就将其赋值为 NULL
(2)malloc/free, fopen/fclose使用时一定要配对,
(3)free/fclose后,记得将指针置为 NULL
至此, 我所知道的调试技术已经介绍完了,这里列举的都是常见的,也是常用的,怎样去灵活运用这些调试技能,还是得靠自己多实践, 记得某位哲人说过: 实践是检验真理的唯一标准
又记得某位哲人说过:没有调查就没有发言权
结束语:怎样尽可能的避免错误
调试的最高境界就是不去调试,怎样才能不去调试或者只需少量调试就能使程序正常跑起来呢?
答案是,深入学习 c语言,培养 c语言 的审美感。怎么说 c语言也是一门语言,话都说不周整的人,
编程能力肯定不怎么样,/君不见,那些大牛们写起帖子来就像是写论文,像RockCarry、Knocker、forever74、jig、rootkit 、rtgirl、starwing83、广陵绝唱..../ (不再一一列举了)他们写的帖子,读起来就是不一样,像是读散文,/
哥最看不上的就是那些只会写两个字还让别人回去琢磨的"高手"...
最后,我引用一下 < c primer plus> 的作者 Stephen Prata的一句话:
如果您想找一份编写软件的工作, 则首先您应该能够回答 "是" 的一个问题就是:"请问,您会使用c吗?"
[ 本帖最后由 BlueGuy 于 2010-12-13 21:00 编辑 ]