Vc6.0 调试指南 --Happy Debugging
一:深入了解 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
Vc6.0 调试指南 --Happy Debugging相关推荐
- VC6.0调试功能使用介绍
VC6.0调试功能使用介绍 程序编译好以后需要进行错误的排查,找到代码问题所在,这就是调试.程序员的调试能力体现了程序员的水平高低,是程序员的重要能力之一. 以下是VC6调试相关介绍: 一: 快捷键 ...
- Windows API 实现查找、删除任意类型的文件_VERSION20120605(vc6.0调试通过)(2012.6.5最新修改)
1 // FileToolsV20120605.cpp : Defines the entry point for the application. 2 // 3 //用vc6.0建立一个win32简 ...
- VC6.0调试知识大全
My Note ○常用功能: Restart(Ctrl+shift+F5):此debugger功能将从程序的开始(第一有效行)处全速执行,而不是从当前所跟踪的位置开始调 试,这时所有变量的当前值都将被 ...
- vc6.0 调试程序,终止调试时程序却无法退出
详情描述: VC6.0发布于1998年,到文章发布的今天已经23年了,但由于他的绿色.小巧,我一直还在使用它. 在使用vc6.0(系统:windows 7 64 bit,Win10 64 bit)调试 ...
- VC6.0环境设置说明
VC环境设置说明 http://blog.sina.com.cn/s/blog_4bb59dc40100fcet.html 所谓:"工欲善其事,必先利其器".如果我们精于VC开发环 ...
- linux内核调试指南
Hunnad的专栏 * 条新通知 * 登录 * 注册 * 欢迎 * 退出 * 我的博客 * 配置 * 写文章 * 文章管理 * 博客首页 * * * * 空间 * 博客 * 好友 * 相册 * 留言 ...
- Linux Kernel - Debug Guide (Linux内核调试指南 )
linux内核调试指南 一些前言 作者前言 知识从哪里来 为什么撰写本文档 为什么需要汇编级调试 ***第一部分:基础知识*** 总纲:内核世界的陷阱 源码阅读的陷阱 代码调试的陷阱 原理理解的陷阱 ...
- GDB调试指南(入门,看这篇够了)
写这篇文档的目的是对前面GDB的知识做一次总览,本文为GDB调试指南,参考GDB调试手册,目前已有的篇目: 启动调试 断点设置 查看源码 单步调试 查看变量 前言 GDB是Linux下非常好用且强大的 ...
- vc6.0 点击鼠标获取mysql数据库所在行_VC6.0连接到mysql数据库
(1)打开VC6.0 工具栏Tools菜单下的Options选项,在Directories的标签页中右边的"Show directories for:"下拉列表中选中"I ...
最新文章
- 我是一个平平无奇的AI神经元
- 比Segway轻便的机器人溜冰鞋
- python使用del保留字定义一个函数-python中自定义函数的保留字是
- 利用反射球实现镜面效果
- Linux下安装LoadRunner LoadGenerator
- mysql二进制升级_MySQL二进制安装,升级,多实例部署
- parseInt和parseFloat(转换成数字,提取成数字)?
- Linux编译程序源码环境,Linux下对nodejs环境进行源码编译并部署云应用
- sql case when then else多个条件_SQL-多表查询
- linux磁盘写保护怎么修改_linux系统怎么避免u盘被写保护
- 学习Java面向对象编程和设计模式最好的5本书
- linux种子搜索关键字,基于 DHT 网络的磁力链接和BT种子的搜索引擎架构
- c语言编写aoi程序,AOI编程步骤
- 计算机光驱无法启用,光驱提示:无法访问G:\函数不正确解决方法
- IT人才供不应求,大数据分析程序员今后的发展道路
- 一千万的股票能一天卖出吗
- Tool:Visio2016/Visio2019专业版64位中文下载、安装(图文教程)之详细攻略
- python网络数据采集学习笔记(二)
- 甲骨文发布适用于 MongoDB 的 Oracle Database API;Chrome 和 Edge 互相“拉踩”;树莓派驱动程序现可在 Android 上运行 | 开源日报
- 基于DYDX闪电贷在Cofix和Uniswap之间套利