头文件中应该放入:
#1 函数原型声明
#2全局变量声明
这里说的是声明,不是定义。即:extern int global;
#3自己定义的宏和类型
而下列是不该放入的:
#1 全局变量和函数的定义
全局变量只能定义一次,如果把int
global;这样的语句放在头文件中,那么所有包含这个头文件的地方都定义一次全局变量global,到了链接的时候链接程序就会报告找到多个global。函数的情形也是这个道理,因为函数的名字在全局空间里面也应该是唯一的。
#2 static 变量和 static 函数
这也是显然的,static本来就是防止全局可见的。
对预编译头文件说明如下:
所谓头文件预编译,就是把一个工程(Project)中使用的一些MFC标准头文件(如Windows.H、Afxwin.H)预先编译,以后该工程编译时,不再编译这部分头文件,仅仅使用预编译的结果。这样可以加快编译速度,节省时间。
预编译头文件通过编译stdafx.cpp生成,以工程名命名,由于预编译的头文件的后缀是“pch”,所以编译结果文件是projectname.pch。
编译器通过一个头文件stdafx.h来使用预编译头文件。stdafx.h这个头文件名是可以在project的编译设置里指定的。编译器认为,所有在指令#include "stdafx.h"前的代码都是预编译的,它跳过#include "stdafx. h"指令,使用projectname.pch编译这条指令之后的所有代码。
因此,所有的CPP实现文件第一条语句都是:#include "stdafx.h"。
另外,每一个实现文件CPP都包含了如下语句:
#ifdef _DEBUG
#undef THIS_FILE
static char BASED_CODE THIS_FILE[] = __FILE__;
#endif
这是表示,如果生成调试版本,要指示当前文件的名称。__FILE__是一个宏,在编译器编译过程中给它赋值为当前正在编译的文件名称。
下面是侯捷老师的说法:
2. 使用 AppWizard Project file 中想要加入 .cpp file
> 在 Compiling 过程会被要求请加入 "stdafx.h"
> 请问可以避免吗?
你是问「一个 project 之中可否不要 stdafx.h」吗?答案是可以,
但你的日子会从此更辛劳些。
随便看一个由 AppWizard 产生的 project,其 stdafx.h 内是一些
必要的 include files(例如 afxwin.h)。对应於 stdafx.h 则有
一个 stdafx.cpp,其内只是 #include "stdafx.h"。这有什麽用意?
用意是,让 stdafx.cpp 编译出一个 stdafx.obj,成为
所谓的 "pre-compile-header"(需搭配特殊 compile option。
AppWizard 会为我们的 project 自动设定这个特殊的 option),
可大幅降低第二次(以降)的编译所需时间(因为标准 MFC headers
不需再编译一次)。
往後,你每使用 ClassWizard 协助开发此程式,ClassWizard 便有
可能在 stdafx.h 中增加新的 include files。例如,你若选用
MFC template classes,stdafx.h 中便会增加 #include 。
如果你不要 afxstd.h,意味你得自己 take care 这些事情。
--- the end
最后要注意的是::
在声明包含stdafx.h的语句之前的语句都将被完全忽略。例如:
abcdefg
#include "stdafx.h"
能编译通过。
所以
#include
#include "stdafx.h"
中,"math.h"也将被忽略。
避免上述错误的方法是将包含语句放在"stdafx.h"之后或者更好的方法是将其放在"stdafx.h"文件中。
一、
自己的用法:
#include //important .c or c++
#ifdef _DEBUG
#include /* vsprintf */
#define DPRINTF_BUF_SZ 1024
static __inline void
DPRINTF(char *fmt, ...)
{
va_list args;
char buf[DPRINTF_BUF_SZ];
va_start(args, fmt);
vsprintf(buf, fmt, args);
OutputDebugString(buf);
}
#else
static __inline void
DPRINTF(char *fmt, ...) { }
#endif
二、
在以前的一篇文章,提到了一个方便的VC的调试类,但是目前在我的一台计算机中需要同时调试3~5个程序,这样的话,到处都是Console的窗口,鼠标到处点击选择自己需要看的窗口。很是麻烦。
其实MS已经为我们提供了一个非常方便的函数来完成调试输出。OutputDebugString.
当然,完全使用这个还有点麻烦,因为不支持变参数的输入。
那,需要改进以下:
程序代码
呵呵,这样的话,我就可以这样加入一个调试信息了
PNTDEBUG("调试第一次:%d",m_iCount);
...
如果你把此函数加入到你的工程,而不显示任何东西,那就对了![:)]
因为你需要一个DebugView程序来接受你的调试信息,你可以从这里下载
这个工具还能支持远程调试信息的接收,很好用。
下面的程式碼是將OutputDebugString()函式將以包裝後,使其具有類似於printf的變數功能。這在顯示除錯資訊時,是非常有用的。使用時,程式設計師可以用DBG(format, …)的方式來顯示字串。要監看這個除錯資訊,您需要透過一些所謂的Debugger程式才能看到。建議使用一個相當有名的免費的Debugger程式叫做DebugView。
另外,在顯示除錯資訊時常常會要顯示關於原始程式檔,函式名稱,行數等資訊。所以,另外定義了幾個常數。方便程式設計師使用。
// dbg.h: The debug header file. #ifndef __DBG_H__ #define __DBG_H__
#include #include #include
#ifdef _DEBUG static void __dbg_printf (const char * format,...) { #define MAX_DBG_MSG_LEN (1024) char buf[MAX_DBG_MSG_LEN]; va_list ap;
va_start(ap, format);
_vsnprintf(buf, sizeof(buf), format, ap); OutputDebugString(buf);
va_end(ap); } #define DBG __dbg_printf #else static void __dbg_printf (const char * format,...) {} #define DBG 1?((void)(NULL)):__dbg_printf #endif
#define _FLP_ "%s:%d: " #define _FL_ __FUNC__, __LINE__
#define _FFLP_ "%s:" _FLP_ #define _FFL_ __FILE__, _FL_
// For example: // DBG(_FLP_ "format string", _FL_, arg1, arg2); // DBG(_FFLP_ "format string", _FFL, arg1, arg2); #endif
四、
最近需要增加一个调试的类在自己开发的P2P系统中,以便我能够随时都看到SOCKET在执行
什么操作了,原来都是在自己的主窗口中再增加一个CEdit用来实时记录当前的状态,但是这个
EDIT又非常影响其他窗口中控件,很是麻烦,嘿,前几天看到CodeProject.com中有个调试的
类,下载来看,非常好用。

它不是在你的窗口中有任何的输入,而是使用一个CONSOLE的控制窗口来显示你的调试记录。
这样,就不会影响到自己开发的窗口的控件。
7.15 数组的大小可以在程序运行时定义吗?
不。在数组的定义中,数组的大小必须是编译时可知的,不能是在程序运行时才可知的。例如,假设i是一个变量,你就不能用i去定义一个数组的大小:
char array[i];
/*(notvalidc */
有些语言支持这种定义,但C语言不支持。如果C语言支持这种定义,栈就会变得更复杂,调用函数的开销就会更大,而程序的运行速度就会明显变慢。
如果数组的大小在编译时是可知的,即使它是一个非常复杂的表达式,只要它在编译时能被计算出来,你就可以定义它。
如果你要使用一个在程序运行时才知道其大小的数组,你可以说明一个指针,并且调用malloc()或calloc()函数从堆中为这个数组分配内存空间。以下是一个拷贝传给main()函数的argv数组的例子:
例 7.15 在动行时确定大小的数组,使用了指针和malloc()
/*
A silly program that copies the argv array and all the pointed-to
strings. Just for fun, it also deallocates all the copies.
*/
# include
# include
int
main (int argc, char* * argv)
{
char* * new_argv;
int i;
/*
Since argv[0] through argv [argc] are all valid,
the
program needs to allocate room for argc + 1
pointers.
*/
new_argv = (char* * ) calloc(argc + l, sizeof
(char * ));
/ * or malloc ((argc +1) * sizeof (char * ) ) *
/
printf ("allocated room for %d pointers starting
at %P\n", argc + 1, new_argv);
/*
now copy all the strings themselves
(argv[0] through argv[argc-l])
*/
for (i = 0;i
{
/ * make room for '\0'
at end, too * /
new_argv [i]= (char* )
malloc(strlen(argv[i]) + l);
strcpy(new_argv[i],
argv[i]);
printf ("allocated %d
bytes for new_argv[%d] at %P",
"copied\"%s\"\n",
strlen(argv[i]) + l, i, new_argv[i], new_argv[i]) ;
}
new_ argv [argc] = NULL:
/*
To deallocate everything, get rid of the strings
(in any
order), then the array of pointers. If you free
the array
of poiners first, you lose all reference to the
copied
strings.
*/
for (i = 0;i
free(new_argv[i]);
printf ("freed
new_argv[%d] at %P\n" , i, new_argv[i]) ;
argv[i]=NULL; /* 习惯,见本例后面的注意 */
}
free(new_argv);
printf("freed new_argv itself at %P\n",new_argv);
return 0; /*请参见16.4 */
}
注意:为什么例7.5在释放了new_argv数组中的每个元素之后,还要将这些元素赋值为NULL呢?这是一种在长期实践的基础上形成的习惯。在释放了一个指针之后,你就无法再使用它原来所指向的数据了,或者说,该指针被“悬挂”起来了,它不再指向任何有用的数据。如果在释放一个指针之后立即将它赋值为NULL,那么,即使程序再次使用该指针,程序也不会出错。当然,程序可能会间接引用这个空指针,但这种错误在调试程序时就能及时被发现。此外,
程序中可能仍然有一些该指针原来的拷贝,它们仍然指向已被释放的那部分内存空间,这种情况在C程序中是很自然的。总之,尽管上述这种习惯并不能解决所有问题,但确实有作用。
请参见:
7.16 用malloc()函数更好还是用calloc()函数更好?
7.20 什么是栈(stack)?
7.21 什么是堆(heap)?
7.22 两次释放一个指针会导致什么结果?
9.8 为什么用const说明的常量不能用来定义一个数组的初始大小?
7.16 用malloc()函数更好还是用calloc()函数更好?
函数malloc()和calloc()都可以用来分配动态内存空间,但两者稍有区别。
malloc()函数有一个参数,即要分配的内存空间的大小:
Void *malloc(size_t size);
calloc()函数有两个参数,分别为元素的数目和每个元素的大小,这两个参数的乘积就是要分配的内存空间的大小:
void *calloc(size_t numElements,size_t sizeOfElement);
如果调用成功,函数malloc()和calloc()都将返回所分配的内存空间的首地址。
malloc()函数和calloc()函数的主要区别是前者不能初始化所分配的内存空间,而后者能。如果由malloc()函数分配的内存空间原来没有被使用过,则其中的每一位可能都是0;反之,如果这部分内存空间曾经被分配、释放和重新分配,则其中可能遗留各种各样的数据。也就是说,使用malloc()函数的程序开始时(内存空间还没有被重新分配)能正常运行,但经过一段时间后(内存空间已被重新分配)可能会出现问题。
calloc()函数会将所分配的内存空间中的每一位都初始化为零,也就是说,如果你是为字符类型或整数类型的元素分配内存,那么这些元素将保证会被初始化为零;如果你是为指针类型的元素分配内存,那么这些元素通常(但无法保证)会被初始化为空指针;如果你是为实数类型的元素分配内存,那么这些元素可能(只在某些计算机中)会被初始化为浮点型的零。
malloc()函数和calloc()函数的另一点区别是calloc()函数会返回一个由某种对象组成的数组,但malloc()函数只返回一个对象。为了明确是为一个数组分配内存空间,有些程序员会选用calloc()函数。但是,除了是否初始化所分配的内存空间这一点之外,绝大多数程序员认
为以下两种函数调用方式没有区别:
calloc(numElements,sizeOfElement);
malloc(numElements *sizeOfElement);
需要解释的一点是,理论上(按照ANSIC标准)指针的算术运算只能在一个指定的数组中进行,但是在实践中,即使C编译程序或翻译器遵循这种规定,许多C程序还是冲破了这种限制。因此,尽管malloc()函数并不能返回一个数组,它所分配的内存空间仍然能供一个数组使用(对realloc()函数来说同样如此,尽管它也不能返回一个数组)。
总之,当你在calloc()函数和malloc()函数之间作选择时,你只需考虑是否要初始化所分配的内存空间,而不用考虑函数是否能返回一个数组。
请参见:
7.7 两个指针可以相减吗?为什么?
7.8 把一个值加到指针上意味着什么?
7.10 NULL总是等于0吗?
7.17 怎样说明一个大于64KB的数组?
保守的回答是,为了程序的可移植性,你不能说明这样一个数组。ANSI/ISOC标准规定编译程序能处理的单个对象的大小不得超过(32KB-1)个字节。
为什么64KB是一种上限呢?因为16位指针的最大寻址空间是64KB。
在有些环境中,你可以直接说明一个大于64KB的数组,这种说明是有效的,不会引起任何问题;在另外一些环境中,你不能说明这样大的一个数组,但你可以调用函数malloc()或calloc()从堆中分配一块这样大的内存空间。
在IBMPC兼容机上,这种限制更严格。在这种情况下,你至少要使用大数据存储模式(见本章开头部分的介绍)。此外,你还要调用函数malloc()或calloc()的"far"变体来分配内存空间。例如,为了分配一块70,000字节的缓冲区,在BorlandC和BorlandC++中,你可以使用以下函数调用形式:
far char *buffer=farmalloc(70000L);
在MicrosoftC和MicrosoftC++中,你可以使用以下函数调用形式:
far char *fbuffer=fmalloc(70000L);
注意:70000L尾部的L用来指明该常量是一个long int型常量。一个int型常量的长度为16位,其中还包括一位符号位,因此存不下70,000这个值。
请参见:
7.18 far和near之间有什么区别?
7.21 什么是堆(heap)?
9.3为什么要小心对待位于数组后面的那些元素的地址呢?
7.18 far和near之间有什么区别?
在本章的开头部分曾经介绍过,基于IBMPC兼容机的一些编译程序使用两种指针,这两种指针就是此处要讨论的far指针和near指针。near指针的长度为16位,可寻址空间为64KB;far指针的长度为32位,可寻址空间为1MB。near指针可在长度为64KB的段中工作,这种段有两种,一种供函数地址使用,即程序段;一种供数据地址使用,即数据段。
far指针含一个16位的基地址(即段地址)和一个16位的偏移量,将基地址乘以16(16进制下右移一位)后再与偏移量相加,即可得·到far指针所指向的地址,因此far指针的实际长度是20位。例如,如果一个far指针的段地址为0x7000,偏移量为0x1224,则该指针指向地址0x71224;如果一个far指针的段地址为0x7122,偏移量为0x0004。则该指针也指向地址0x71224。
在编译你的程序之前,你必须指示编译程序使用哪种存储模式。如果使用小代码存储模式,指向函数地址的指针将被默认为near指针,也就是说,所有的函数都要在同‘个64KB的段中;如果使用大代码存储模式,指向函数地址的指针将被默认为far指针。同样,对小和大数据存储模式来说,指向数据地址的指针也将分别被默认为near指针和far指针。除了默认的near指针或far指针之外,你仍然可以明确地将指向函数或数据的指针说明为near指针或far指针。
far指针工作起来相对慢一些,因为每次访问一个{ar指针时,都要将数据段或程序段的段寄存器中的数据交换出来。far指针的算术运算和比较也有些反常,例如,前文例子中提到的两个far指针指向同一个地址,但两者比较的结果却不相同。如果你的程序使用了小数据和小代码存储模式,那么它运行起来就相当快;否则,它的运行速度就会明显变慢。
除了上述内容之处,你的编译程序对near指针和far指针往往还有一些细节性的规定,请阅读编译程序文档,以了解更详细的信息。
请参见:
7.19什么时候使用far指针?
7.19 什么时候使用far指针?
当使用小代码或小数据存储模式时,也许程序的大部分函数或数据能装入同一个程序段或数据段中,但仍有一部分函数或数据无法装入,这时,你就要为这些剩余的函数或数据另外分配内存空间,并显式说明相应的far函数或far指针来使用这部分内存空间。在使用小代码存储模式时绝大多数函数被限制在64KB的程序内,但far函数可以使用64KB的程序段外的内存空间(许多C库函数都被显式说明为far函数,因此无论程序使用哪种类型的代码存储模式,这些函数都能正常工作)。同样,far指针能使用64KB的数据段外的内存空间,并且通常都和farmalloc()这样的函数一起使用,以管理64KB的数据段以外的堆。
请参见;
7.18far和near之间有什么区别?
7.21什么是堆(heap)?
7.20 什么是栈(stack)?
栈是存放函数的所有动态局部变量及函数调用和返回的有关信息的一块内存。调试程序提供了一种功能,能够显示一个已被调用的函数的列表,这是调试程序时一种很有用的手段。
栈的内存管理严格遵循先进后出的顺序,即释放栈中对象所占内存时的顺序刚好与给这些对象分配栈中内存时的顺序相反,这一点正是实现函数调用所需要的。从栈中分配内存效率特别高,C编译程序能产生如此优质的代码的原因之一,就是充分利用了栈。
较早版本的C语言曾提供这样一个函数,程序员可以用它从栈中分配内存,而该函数返回时会自动释放所分配的内存。这种函数调用是非常危险的,因此版本较新的C语言中不再提供这个函数。
请参见:
7.15 数组的大小可以在程序运行时定义吗?
7.2l 什么是堆(heap)?
7.21 什么是堆(heap)?
堆是供malloc(),calloc()和realloc()等函数获取内存空间的一块内存。
从堆中获取内存比从栈中获取内存要慢得多,但是堆的内存管理却比栈灵活得多,任何时候你都可以从堆中获取内存,而且在释放堆中对象所占的内存时,你可以按任意顺序进行。数据对象使用栈中内存(如动态局部变量)比使用堆中内存会使程序运行更快,但是有时使用堆中内存可以改善一种算法,例如使它更快,或者鲁棒性(强壮性)更好,或者更灵活。因此,使用堆还是栈需要折衷考虑。
用来存放递归数据结构的内存几乎都要从堆中获取。用来存放字符串的内存通常也从堆中获取,尤其是对那些在程序运行时可能出现的很长的字符串。
从堆中获取的内存不会被自动释放,因此你必须调用free()函数释放这种内存。如果从堆中获取的内存在使用完后没有被释放,这部分内存在程序结束之前会一直被占用,这种情况被称为“内存漏洞”。如果内存漏洞发生在一个循环中,你很快就会用光堆中的内存(此时内存分配函数会返回一个空指针)。在有些环境中,如果一个程序没有将它所用过的内存释放掉,这部分内存在该程序结束之后仍然会一直被占用。
注意:在调试程序时很难发现内存漏洞,但可以借助内存分配工具来发现它。
有些编程语言不支持用户程序释放堆中的内存,而是采用了一种自动“内存垃圾”的方式。这种方式会导致一些很严重的性能问题,并且实现起来也要困难得多,程序的开销往往也更多。C语言也有一些自动内存垃圾的函数,但用这些函数来管理内存是非常危险的。这是一个涉及到设计编译程序的问题,此处不再详细讨论。
请参见:
7.4什么时候使用空指针?
7.20什么是栈(stack)?
7.22 两次释放一个指针会导致什么结果?
如果你释放了一个指针,又重新给它分配内存,然后再次释放它,这样做是安全的。
注意;准确地说,当我们说释放一个指针时,实际上指的是释放该指针所指向的内存,而不是指针本身,因为指针本身不会因此而有任何变化。然而,C程序员通常都把释放一个指针所指向的内存简短地说成是释放一个指针。
当你释放了一个指针后,所释放的内存可能刚好会被重新分配给另外一个指针,在这种情况下,你就可以再次使用原来那个指针,并且可以再次释放这个指针,当然,这种情况极其罕见。请看下例:
# include
int
main(int argc, char* * argv)
{
char* * new_argv1;
char* * new_argv2;
new_argvl = calloc(argc +1, sizeofCchar * )) ;
free(new-argvl) ; /* free once */ .
new_argv 2 = (char* * ) calloc (argc+1,
sizeof(char*));
if (new_argvl == new_argv2) {
/*
new-argvl accidentally
points to freeable mem
*/
free(new_argvl) ; /*
freed twice */
} else {
free(new_argv2);
}
new_argvl = calloc(argc + l, sizeof(char*));
free(new_argvl) ; /* freed once again */
return ();
}
上例没有什么实际意义,它只用来说明在什么样的情况下两次释放一个指针是安全的。在上例中,第一次给指针new_argv1分配一块能拷贝argv数组的内存后,立即将其释放,然后将同样大小的一块内存分配给指针new_argv2。因为此时第一块内存已经是空闲的,所以calloc()函数可能会刚好将第一块内存分配给指针Hew_argv2。在这种情况下;new_argvl和new_argv2将指向同一块内存,它们在程序中是等价的。因此,在if语句的第一个子句中,free()函数再次释放了new_argvl,其作用和释放new_argv2是相同的。上例说明,在合法的前提下,一个指针可以被释放任意多次。
然而,如果你释放了一个指针,在没有重新给它分配内存之前再次释放它,会导致什么结果呢?请看下例:
void caller(...)
{
void *p;
/* ... */
callee(p);
free(p);
}
void callee(void *p)
{
/* ... */
free(p)'
return;
}
在上例中,caller()函数将指针p传递给callee()函数后就释放了p,而callee()函数也释放了p,这样,p所指向的内存就被连续释放了两次。ANSIC标准不知道怎样处理这种操作方式,因此这会导致难以预料并且通常都是破坏性的结果。
在编写内存分配和释放函数时,可以让它们检查哪些内存正在被使用或已经被释放,但为了使这些函数工作起来更快,通常都没有这样做。当用free()函数释放一个指针时,它总是认为该指针所指向的内存是由malloc()函数或calloc()函数分配的,并且分配之后还从没有被释放过。free()函数将计算这块内存的大小,并且更新其中的数据结构,不管这块内存是否已经被释放过。如果这块内存已经被释放过,那么free()函数就很可能会将某些信息写到一个错误的位置上去,从而使你的程序中出现一个野指针,从此你的程序就陷入困境了(请参见本章开头部分的介绍)。
为了有效地防止两次释放一个指针,首先要仔细编写程序,其次要利用一些内存分配工具来检查程序中是否存在这种错误。
请参见:
7.21 什么是堆(heap)?
7.24 为什么不能给空指针赋值?什么是总线错误、内存错误和内存信息转储?
7.26 free()函数是怎样知道要释放的内存块的大小的?
7.23 NULL和NUL有什么不同?
NULL是在.h>头文件中专门为空指针定义的一个宏。NUL是ASCII字符集中第一个字符的名称,它对应于一个零值。C语言中没有NUL这样的预定义宏,但有些程序员喜欢定义这样一个宏。
注意;在ASCII字符集中,数字0对应于十进制值80,不要把数字0和'\0'(NUL)的值混同起来。
NULL可以被定义为(void *)0,而NUL可以被定义为'\0'。NULL和NUL都可以被简单地定义为0,这时它们是等价的,可以互换使用,但这是一种不可取的方式。为了使程序读起来更清晰,维护起来更容易,你在程序中应该明确地将NULL定义为指针类型,而将NUL定
义为字符类型。
请参见:
7.3什么是空指针?
7.24 为什么不能给空指针赋值?什么是总线错误、内存错误和内存信息转储?
这些都是程序中存在野指针或越界下标等严重错误时系统所报告的出错消息。
“null pointer assignment”是一个MS-DOS程序执行完毕后可能报告的一条出错消息。有些MS-DOS程序会分配一小块内存给空指针,让空指针也能“指向内存中的某个地址”,如果这些程序试图往这块内存中写入数据,就会覆盖掉原来由编译程序写在其中的数据。当程序执行完毕时,由编译程序生成的一部分代码会检查这块内存中的数据,如果发现原来的数据被修改了,就会报告"null’pointerassignment”这条消息。这条消息告诉你程序中存在野指针或越
界下标,但你无法判断究竟错误出在程序中的哪一部分。有些调试程序和编译程序会提供一些支持,帮助你找出错误。
“Bus error:core dumped”和"Memory fault:core dumped”是运行在UNIX下的程序可能报告的两条出错消息,它们往往出现在程序进行读或写的过程中,都说明程序中存在野指针或越界下标,但所涉及的问题不仅仅局限于空指针。这两条出错消息对程序员更友好,因为“core dumped”这段消息告诉程序员已经将一个名为"core”的文件写入当前目录中,该文件记录了程序出错时栈和堆中的所有信息。借助于调试程序,程序员可以通过“core”文件找出程序
中的野指针或越界下标。尽管这种方式并不告诉程序员出现野指针或越界下标的原因,但它能有效地帮助程序员解决问题。需要注意的是,如果程序未获得写当前目录的许可,就无法产生“core”文件,也就不会报告"core dumped”这段消息。
注意;“core”是指磁芯,因为运行第一代UNIX系统的硬件使用磁芯而不是硅片作为随机存取存贮器。
能帮助你发现内存分配错误的工具,有时同样能帮助你找出程序中的野指针和越界下标。
在最好的情况下,这些工具几乎能找出所有这类错误。
请参见:
7.3 什么是空指针?
7.25 怎样确定一块已分配的内存的大小?
实际上你无法确定。free()函数能做到这一点,但你的程序无法知道free()函数所用的技巧。尽管通过反汇编库函数你可能会发现这种技巧,但是在不同版本的编译程序中,这种技巧可能会有所不同,因此这样做没有什么意义?
请参见:
7