一:深入了解 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相关推荐

  1. VC6.0调试功能使用介绍

    VC6.0调试功能使用介绍 程序编译好以后需要进行错误的排查,找到代码问题所在,这就是调试.程序员的调试能力体现了程序员的水平高低,是程序员的重要能力之一. 以下是VC6调试相关介绍: 一: 快捷键 ...

  2. Windows API 实现查找、删除任意类型的文件_VERSION20120605(vc6.0调试通过)(2012.6.5最新修改)

    1 // FileToolsV20120605.cpp : Defines the entry point for the application. 2 // 3 //用vc6.0建立一个win32简 ...

  3. VC6.0调试知识大全

    My Note ○常用功能: Restart(Ctrl+shift+F5):此debugger功能将从程序的开始(第一有效行)处全速执行,而不是从当前所跟踪的位置开始调 试,这时所有变量的当前值都将被 ...

  4. vc6.0 调试程序,终止调试时程序却无法退出

    详情描述: VC6.0发布于1998年,到文章发布的今天已经23年了,但由于他的绿色.小巧,我一直还在使用它. 在使用vc6.0(系统:windows 7 64 bit,Win10 64 bit)调试 ...

  5. VC6.0环境设置说明

    VC环境设置说明 http://blog.sina.com.cn/s/blog_4bb59dc40100fcet.html 所谓:"工欲善其事,必先利其器".如果我们精于VC开发环 ...

  6. linux内核调试指南

    Hunnad的专栏 * 条新通知 * 登录 * 注册 * 欢迎 * 退出 * 我的博客 * 配置 * 写文章 * 文章管理 * 博客首页 * * * * 空间 * 博客 * 好友 * 相册 * 留言 ...

  7. Linux Kernel - Debug Guide (Linux内核调试指南 )

    linux内核调试指南 一些前言 作者前言 知识从哪里来 为什么撰写本文档 为什么需要汇编级调试 ***第一部分:基础知识*** 总纲:内核世界的陷阱 源码阅读的陷阱 代码调试的陷阱 原理理解的陷阱 ...

  8. GDB调试指南(入门,看这篇够了)

    写这篇文档的目的是对前面GDB的知识做一次总览,本文为GDB调试指南,参考GDB调试手册,目前已有的篇目: 启动调试 断点设置 查看源码 单步调试 查看变量 前言 GDB是Linux下非常好用且强大的 ...

  9. vc6.0 点击鼠标获取mysql数据库所在行_VC6.0连接到mysql数据库

    (1)打开VC6.0 工具栏Tools菜单下的Options选项,在Directories的标签页中右边的"Show directories for:"下拉列表中选中"I ...

最新文章

  1. 我是一个平平无奇的AI神经元
  2. 比Segway轻便的机器人溜冰鞋
  3. python使用del保留字定义一个函数-python中自定义函数的保留字是
  4. 利用反射球实现镜面效果
  5. Linux下安装LoadRunner LoadGenerator
  6. mysql二进制升级_MySQL二进制安装,升级,多实例部署
  7. parseInt和parseFloat(转换成数字,提取成数字)?
  8. Linux编译程序源码环境,Linux下对nodejs环境进行源码编译并部署云应用
  9. sql case when then else多个条件_SQL-多表查询
  10. linux磁盘写保护怎么修改_linux系统怎么避免u盘被写保护
  11. 学习Java面向对象编程和设计模式最好的5本书
  12. linux种子搜索关键字,基于 DHT 网络的磁力链接和BT种子的搜索引擎架构
  13. c语言编写aoi程序,AOI编程步骤
  14. 计算机光驱无法启用,光驱提示:无法访问G:\函数不正确解决方法
  15. IT人才供不应求,大数据分析程序员今后的发展道路
  16. 一千万的股票能一天卖出吗
  17. Tool:Visio2016/Visio2019专业版64位中文下载、安装(图文教程)之详细攻略
  18. python网络数据采集学习笔记(二)
  19. 甲骨文发布适用于 MongoDB 的 Oracle Database API;Chrome 和 Edge 互相“拉踩”;树莓派驱动程序现可在 Android 上运行 | 开源日报
  20. 基于DYDX闪电贷在Cofix和Uniswap之间套利

热门文章

  1. The constructor someMethod() is not accessible due to restriction on required library
  2. HTML-form标签学习_015
  3. spring之基本介绍以及老版本框架的下载地址
  4. 正确清理binlog日志
  5. 学习HTML5+CSS3的第一天
  6. AutoFac文档9(转载)
  7. JSF是什么?它与Struts是什么关系?
  8. zabbix-自定义监控项
  9. tornado框架介绍
  10. Grafana展示DNS解析延时