road420

导航

<2009年10月>
27282930123
45678910
11121314151617
18192021222324
25262728293031
1234567

统计

常用链接

留言簿(2)

随笔档案

文章档案

搜索

最新评论

阅读排行榜

评论排行榜

#

#define

今天整理了一些#define的用法,与大家共享!
1.简单的define定义

#define MAXTIME 1000 

一个简单的MAXTIME就定义好了,它代表1000,如果在程序里面写

if(i<MAXTIME){.........}

编译器在处理这个代码之前会对MAXTIME进行处理替换为1000。

这样的定义看起来类似于普通的常量定义CONST,但也有着不同,因为define的定义更像是简单的文本替换,而不是作为一个量来使用,这个问题在下面反映的尤为突出。

2.define的“函数定义”

define可以像函数那样接受一些参数,如下

#define max(x,y) (x)>(y)?(x):(y);

这个定义就将返回两个数中较大的那个,看到了吗?因为这个“函数”没有类型检查,就好像一个函数模板似的,当然,它绝对没有模板那么安全就是了。可以作为一个简单的模板来使用而已。

但是这样做的话存在隐患,例子如下:
#define Add(a,b) a+b;
在一般使用的时候是没有问题的,但是如果遇到如:c * Add(a,b) * d的时候就会出现问题,代数式的本意是a+b然后去和c,d相乘,但是因为使用了define(它只是一个简单的替换),所以式子实际上变成了
c*a + b*d

另外举一个例子:
#define pin (int*);
pin a,b;
本意是a和b都是int型指针,但是实际上变成int* a,b;
a是int型指针,而b是int型变量。
这是应该使用typedef来代替define,这样a和b就都是int型指针了。

所以我们在定义的时候,养成一个良好的习惯,建议所有的层次都要加括号。

3.宏的单行定义
#define A(x) T_##x
#define B(x) #@x

#define C(x) #x
我们假设:x=1,则有:
A(1)------〉T_1
B(1)------〉'1'
C(1)------〉"1"

(这里参考了 hustli的文章)

3.define的多行定义

define可以替代多行的代码,例如MFC中的宏定义(非常的经典,虽然让人看了恶心)

#define MACRO(arg1, arg2) do { \
/* declarations */ \
stmt1; \
stmt2; \
/* ... */ \
} while(0) /* (no trailing ; ) */

#define DECLARE_RTTI(thisClass, superClass)\
  virtual const char* GetClassName() const\
  {return #thisClass;}\
  static int isTypeOf(const char* type)\
  {\
   if(!strcmp(#thisClass, type)\
    return 1;\
   return superClass::isTypeOf(type);\
   return 0;\
  }\
  virtual int isA(const char* type)\
  {\
   return thisClass::isTypeOf(type);\
  }\
  static thisClass* SafeDownCast(DitkObject* o)\
  {\
   if(o&&o->isA(#thisClass))\
    return static_cast<thisClass*>(o);\
   return NULL;\
  }

关键是要在每一个换行的时候加上一个"\" 

摘抄自http://www.blog.edu.cn/user1/16293/archives/2005/115370.shtml 修补了几个bug

4.在大规模的开发过程中,特别是跨平台和系统的软件里,define最重要的功能是条件编译。

就是:
#ifdef WINDOWS
......
......
#endif
#ifdef LINUX
......
......
#endif

可以在编译的时候通过#define设置编译环境

5.如何定义宏、取消宏

//定义宏
#define [MacroName] [MacroValue]
//取消宏
#undef [MacroName]
普通宏
#define PI (3.1415926)

带参数的宏
#define max(a,b) ((a)>(b)? (a),(b))
关键是十分容易产生错误,包括机器和人理解上的差异等等。

6.条件编译
#ifdef XXX…(#else) …#endif
例如 #ifdef DV22_AUX_INPUT
#define AUX_MODE 3 
#else
#define AUY_MODE 3
#endif
#ifndef XXX … (#else) … #endif

7.头文件(.h)可以被头文件或C文件包含;
重复包含(重复定义)
由于头文件包含可以嵌套,那么C文件就有可能包含多次同一个头文件,就可能出现重复定义的问题的。
通过条件编译开关来避免重复包含(重复定义)
例如
#ifndef __headerfileXXX__
#define __headerfileXXX__

文件内容

#endif

posted @ 2008-07-09 08:48 深邃者 阅读(240) | 评论 (0)编辑 收藏

回调函数

简介

  对于很多初学者来说,往往觉得回调函数很神秘,很想知道回调函数的工作原理。本文将要解释什么是回调函数、它们有什么好处、为什么要使用它们等等问题,在开始之前,假设你已经熟知了函数指针。

  什么是回调函数?

  简而言之,回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用为调用它所指向的函数时,我们就说这是回调函数。

  为什么要使用回调函数?

  因为可以把调用者与被调用者分开。调用者不关心谁是被调用者,所有它需知道的,只是存在一个具有某种特定原型、某些限制条件(如返回值为int)的被调用函数。

  如果想知道回调函数在实际中有什么作用,先假设有这样一种情况,我们要编写一个库,它提供了某些排序算法的实现,如冒泡排序、快速排序、shell排序、shake排序等等,但为使库更加通用,不想在函数中嵌入排序逻辑,而让使用者来实现相应的逻辑;或者,想让库可用于多种数据类型(int、float、string),此时,该怎么办呢?可以使用函数指针,并进行回调。

  回调可用于通知机制,例如,有时要在程序中设置一个计时器,每到一定时间,程序会得到相应的通知,但通知机制的实现者对我们的程序一无所知。而此时,就需有一个特定原型的函数指针,用这个指针来进行回调,来通知我们的程序事件已经发生。实际上,SetTimer() API使用了一个回调函数来通知计时器,而且,万一没有提供回调函数,它还会把一个消息发往程序的消息队列。

  另一个使用回调机制的API函数是EnumWindow(),它枚举屏幕上所有的顶层窗口,为每个窗口调用一个程序提供的函数,并传递窗口的处理程序。如果被调用者返回一个值,就继续进行迭代,否则,退出。EnumWindow()并不关心被调用者在何处,也不关心被调用者用它传递的处理程序做了什么,它只关心返回值,因为基于返回值,它将继续执行或退出。

  不管怎么说,回调函数是继续自C语言的,因而,在C++中,应只在与C代码建立接口,或与已有的回调接口打交道时,才使用回调函数。除了上述情况,在C++中应使用虚拟方法或函数符(functor),而不是回调函数。

  一个简单的回调函数实现

  下面创建了一个sort.dll的动态链接库,它导出了一个名为CompareFunction的类型--typedef int (__stdcall *CompareFunction)(const byte*, const byte*),它就是回调函数的类型。另外,它也导出了两个方法:Bubblesort()和Quicksort(),这两个方法原型相同,但实现了不同的排序算法。

void DLLDIR __stdcall Bubblesort(byte* array,int size,int elem_size,CompareFunction cmpFunc);

void DLLDIR __stdcall Quicksort(byte* array,int size,int elem_size,CompareFunction cmpFunc);

  这两个函数接受以下参数:

  ·byte * array:指向元素数组的指针(任意类型)。

  ·int size:数组中元素的个数。

  ·int elem_size:数组中一个元素的大小,以字节为单位。

  ·CompareFunction cmpFunc:带有上述原型的指向回调函数的指针。

  这两个函数的会对数组进行某种排序,但每次都需决定两个元素哪个排在前面,而函数中有一个回调函数,其地址是作为一个参数传递进来的。对编写者来说,不必介意函数在何处实现,或它怎样被实现的,所需在意的只是两个用于比较的元素的地址,并返回以下的某个值(库的编写者和使用者都必须遵守这个约定):

  ·-1:如果第一个元素较小,那它在已排序好的数组中,应该排在第二个元素前面。

  ·0:如果两个元素相等,那么它们的相对位置并不重要,在已排序好的数组中,谁在前面都无所谓。

  ·1:如果第一个元素较大,那在已排序好的数组中,它应该排第二个元素后面。

  基于以上约定,函数Bubblesort()的实现如下,Quicksort()就稍微复杂一点:

void DLLDIR __stdcall Bubblesort(byte* array,int size,int elem_size,CompareFunction cmpFunc)
{
 for(int i=0; i < size; i++)
 {
  for(int j=0; j < size-1; j++)
  {
   //回调比较函数
   if(1 == (*cmpFunc)(array+j*elem_size,array+(j+1)*elem_size))
   {
    //两个相比较的元素相交换
    byte* temp = new byte[elem_size];
    memcpy(temp, array+j*elem_size, elem_size);
    memcpy(array+j*elem_size,array+(j+1)*elem_size,elem_size);
    memcpy(array+(j+1)*elem_size, temp, elem_size);
    delete [] temp;
   }
  }
 }
}

  注意:因为实现中使用了memcpy(),所以函数在使用的数据类型方面,会有所局限。

  对使用者来说,必须有一个回调函数,其地址要传递给Bubblesort()函数。下面有二个简单的示例,一个比较两个整数,而另一个比较两个字符串:

int __stdcall CompareInts(const byte* velem1, const byte* velem2)
{
 int elem1 = *(int*)velem1;
 int elem2 = *(int*)velem2;

 if(elem1 < elem2)
  return -1;
 if(elem1 > elem2)
  return 1;

 return 0;
}

int __stdcall CompareStrings(const byte* velem1, const byte* velem2)
{
 const char* elem1 = (char*)velem1;
 const char* elem2 = (char*)velem2;
 return strcmp(elem1, elem2);
}

  下面另有一个程序,用于测试以上所有的代码,它传递了一个有5个元素的数组给Bubblesort()和Quicksort(),同时还传递了一个指向回调函数的指针。

int main(int argc, char* argv[])
{
 int i;
 int array[] = {5432, 4321, 3210, 2109, 1098};

 cout << "Before sorting ints with Bubblesort\n";
 for(i=0; i < 5; i++)
  cout << array[i] << '\n';

 Bubblesort((byte*)array, 5, sizeof(array[0]), &CompareInts);

 cout << "After the sorting\n";
 for(i=0; i < 5; i++)
  cout << array[i] << '\n';

 const char str[5][10] = {"estella","danielle","crissy","bo","angie"};

 cout << "Before sorting strings with Quicksort\n";
 for(i=0; i < 5; i++)
  cout << str[i] << '\n';

 Quicksort((byte*)str, 5, 10, &CompareStrings);

 cout << "After the sorting\n";
 for(i=0; i < 5; i++)
  cout << str[i] << '\n';

 return 0;
}

  如果想进行降序排序(大元素在先),就只需修改回调函数的代码,或使用另一个回调函数,这样编程起来灵活性就比较大了。

调用约定

  上面的代码中,可在函数原型中找到__stdcall,因为它以双下划线打头,所以它是一个特定于编译器的扩展,说到底也就是微软的实现。任何支持开发基于Win32的程序都必须支持这个扩展或其等价物。以__stdcall标识的函数使用了标准调用约定,为什么叫标准约定呢,因为所有的Win32 API(除了个别接受可变参数的除外)都使用它。标准调用约定的函数在它们返回到调用者之前,都会从堆栈中移除掉参数,这也是Pascal的标准约定。但在C/C++中,调用约定是调用者负责清理堆栈,而不是被调用函数;为强制函数使用C/C++调用约定,可使用__cdecl。另外,可变参数函数也使用C/C++调用约定。

  Windows操作系统采用了标准调用约定(Pascal约定),因为其可减小代码的体积。这点对早期的Windows来说非常重要,因为那时它运行在只有640KB内存的电脑上。

  如果你不喜欢__stdcall,还可以使用CALLBACK宏,它定义在windef.h中:

#define CALLBACK __stdcallor

#define CALLBACK PASCAL //而PASCAL在此被#defined成__stdcall

  作为回调函数的C++方法

  因为平时很可能会使用到C++编写代码,也许会想到把回调函数写成类中的一个方法,但先来看看以下的代码:

class CCallbackTester
{
 public:
 int CALLBACK CompareInts(const byte* velem1, const byte* velem2);
};

Bubblesort((byte*)array, 5, sizeof(array[0]),
&CCallbackTester::CompareInts);

  如果使用微软的编译器,将会得到下面这个编译错误:

error C2664: 'Bubblesort' : cannot convert parameter 4 from 'int (__stdcall CCallbackTester::*)(const unsigned char *,const unsigned char *)' to 'int (__stdcall *)(const unsigned char *,const unsigned char *)' There is no context in which this conversion is possible

  这是因为非静态成员函数有一个额外的参数:this指针,这将迫使你在成员函数前面加上static。当然,还有几种方法可以解决这个问题,但限于篇幅,就不再论述了。

posted @ 2008-07-05 10:11 深邃者 阅读(329) | 评论 (1)编辑 收藏

内存分区

五大内存分区
    在C++中,内存分成5个区,他们分别是堆、栈、自由存储区、全局/静态存储区和常量存储区。
    栈,就是那些由编译器在需要的时候分配,在不需要的时候自动清楚的变量的存储区。里面的变量通常是局部变量、函数参数等。
    堆,就是那些由new分配的内存块,他们的释放编译器不去管,由我们的应用程序去控制,一般一个new就要对应一个delete。如果程序员没有释放掉,那么在程序结束后,操作系统会自动回收。
    自由存储区,就是那些由malloc等分配的内存块,他和堆是十分相似的,不过它是用free来结束自己的生命的。
    全局/静态存储区,全局变量和静态变量被分配到同一块内存中,在以前的C语言中,全局变量又分为初始化的和未初始化的,在C++里面没有这个区分了,他们共同占用同一块内存区。
    常量存储区,这是一块比较特殊的存储区,他们里面存放的是常量,不允许修改(当然,你要通过非正当手段也可以修改,而且方法很多)
明确区分堆与栈
    在bbs上,堆与栈的区分问题,似乎是一个永恒的话题,由此可见,初学者对此往往是混淆不清的,所以我决定拿他第一个开刀。
    首先,我们举一个例子:
    void f() { int* p=new int[5]; }
    这条短短的一句话就包含了堆与栈,看到new,我们首先就应该想到,我们分配了一块堆内存,那么指针p呢?他分配的是一块栈内存,所以这句话的意思就是:在栈内存中存放了一个指向一块堆内存的指针p。在程序会先确定在堆中分配内存的大小,然后调用operator new分配内存,然后返回这块内存的首地址,放入栈中,他在VC6下的汇编代码如下:
    00401028   push        14h
    0040102A   call        operator new (00401060)
    0040102F   add         esp,4
    00401032   mov         dword ptr [ebp-8],eax
    00401035   mov         eax,dword ptr [ebp-8]
    00401038   mov         dword ptr [ebp-4],eax
    这里,我们为了简单并没有释放内存,那么该怎么去释放呢?是delete p么?澳,错了,应该是delete []p,这是为了告诉编译器:我删除的是一个数组,VC6就会根据相应的Cookie信息去进行释放内存的工作。
    好了,我们回到我们的主题:堆和栈究竟有什么区别?
    主要的区别由以下几点:
    1、管理方式不同;
    2、空间大小不同;
    3、能否产生碎片不同;
    4、生长方向不同;
    5、分配方式不同;
    6、分配效率不同;
    管理方式:对于栈来讲,是由编译器自动管理,无需我们手工控制;对于堆来说,释放工作由程序员控制,容易产生memory leak。
    空间大小:一般来讲在32位系统下,堆内存可以达到4G的空间,从这个角度来看堆内存几乎是没有什么限制的。但是对于栈来讲,一般都是有一定的空间大小的,例如,在VC6下面,默认的栈空间大小是1M(好像是,记不清楚了)。当然,我们可以修改:   
    打开工程,依次操作菜单如下:Project->Setting->Link,在Category 中选中Output,然后在Reserve中设定堆栈的最大值和commit。
注意:reserve最小值为4Byte;commit是保留在虚拟内存的页文件里面,它设置的较大会使栈开辟较大的值,可能增加内存的开销和启动时间。
    碎片问题:对于堆来讲,频繁的new/delete势必会造成内存空间的不连续,从而造成大量的碎片,使程序效率降低。对于栈来讲,则不会存在这个问题,因为栈是先进后出的队列,他们是如此的一一对应,以至于永远都不可能有一个内存块从栈中间弹出,在他弹出之前,在他上面的后进的栈内容已经被弹出,详细的可以参考数据结构,这里我们就不再一一讨论了。
    生长方向:对于堆来讲,生长方向是向上的,也就是向着内存地址增加的方向;对于栈来讲,它的生长方向是向下的,是向着内存地址减小的方向增长。
    分配方式:堆都是动态分配的,没有静态分配的堆。栈有2种分配方式:静态分配和动态分配。静态分配是编译器完成的,比如局部变量的分配。动态分配由alloca函数进行分配,但是栈的动态分配和堆是不同的,他的动态分配是由编译器进行释放,无需我们手工实现。
    分配效率:栈是机器系统提供的数据结构,计算机会在底层对栈提供支持:分配专门的寄存器存放栈的地址,压栈出栈都有专门的指令执行,这就决定了栈的效率比较高。堆则是C/C++函数库提供的,它的机制是很复杂的,例如为了分配一块内存,库函数会按照一定的算法(具体的算法可以参考数据结构/操作系统)在堆内存中搜索可用的足够大小的空间,如果没有足够大小的空间(可能是由于内存碎片太多),就有可能调用系统功能去增加程序数据段的内存空间,这样就有机会分到足够大小的内存,然后进行返回。显然,堆的效率比栈要低得多。
    从这里我们可以看到,堆和栈相比,由于大量new/delete的使用,容易造成大量的内存碎片;由于没有专门的系统支持,效率很低;由于可能引发用户态和核心态的切换,内存的申请,代价变得更加昂贵。所以栈在程序中是应用最广泛的,就算是函数的调用也利用栈去完成,函数调用过程中的参数,返回地址,EBP和局部变量都采用栈的方式存放。所以,我们推荐大家尽量用栈,而不是用堆。
    虽然栈有如此众多的好处,但是由于和堆相比不是那么灵活,有时候分配大量的内存空间,还是用堆好一些。
    无论是堆还是栈,都要防止越界现象的发生(除非你是故意使其越界),因为越界的结果要么是程序崩溃,要么是摧毁程序的堆、栈结构,产生以想不到的结果,就算是在你的程序运行过程中,没有发生上面的问题,你还是要小心,说不定什么时候就崩掉,那时候debug可是相当困难的:)
    对了,还有一件事,如果有人把堆栈合起来说,那它的意思是栈,可不是堆,呵呵,清楚了?
static用来控制变量的存储方式和可见性
       函数内部定义的变量,在程序执行到它的定义处时,编译器为它在栈上分配空间,函数在栈上分配的空间在此函数执行结束时会释放掉,这样就产生了一个问题: 如果想将函数中此变量的值保存至下一次调用时,如何实现? 最容易想到的方法是定义一个全局的变量,但定义为一个全局变量有许多缺点,最明显的缺点是破坏了此变量的访问范围(使得在此函数中定义的变量,不仅仅受此函数控制)。

       需要一个数据对象为整个类而非某个对象服务,同时又力求不破坏类的封装性,即要求此成员隐藏在类的内部,对外不可见。

       static的内部机制:
       静态数据成员要在程序一开始运行时就必须存在。因为函数在程序运行中被调用,所以静态数据成员不能在任何函数内分配空间和初始化。
       这样,它的空间分配有三个可能的地方,一是作为类的外部接口的头文件,那里有类声明;二是类定义的内部实现,那里有类的成员函数定义;三是应用程序的main()函数前的全局数据声明和定义处。
      静态数据成员要实际地分配空间,故不能在类的声明中定义(只能声明数据成员)。类声明只声明一个类的“尺寸和规格”,并不进行实际的内存分配,所以在类声明中写成定义是错误的。它也不能在头文件中类声明的外部定义,因为那会造成在多个使用该类的源文件中,对其重复定义。
      static被引入以告知编译器,将变量存储在程序的静态存储区而非栈上空间,静态
数据成员按定义出现的先后顺序依次初始化,注意静态成员嵌套时,要保证所嵌套的成员已经初始化了。消除时的顺序是初始化的反顺序。

       static的优势:
       可以节省内存,因为它是所有对象所公有的,因此,对多个对象来说,静态数据成员只存储一处,供所有对象共用。静态数据成员的值对每个对象都是一样,但它的值是可以更新的。只要对静态数据成员的值更新一次,保证所有对象存取更新后的相同的值,这样可以提高时间效率。

        引用静态数据成员时,采用如下格式:
         <类名>::<静态成员名>
    如果静态数据成员的访问权限允许的话(即public的成员),可在程序中,按上述格式
来引用静态数据成员。

       PS:
      (1)类的静态成员函数是属于整个类而非类的对象,所以它没有this指针,这就导致
了它仅能访问类的静态数据和静态成员函数。
      (2)不能将静态成员函数定义为虚函数。
      (3)由于静态成员声明于类中,操作于其外,所以对其取地址操作,就多少有些特殊
,变量地址是指向其数据类型的指针 ,函数地址类型是一个“nonmember函数指针”。

      (4)由于静态成员函数没有this指针,所以就差不多等同于nonmember函数,结果就
产生了一个意想不到的好处:成为一个callback函数,使得我们得以将C++和C-based X W
indow系统结合,同时也成功的应用于线程函数身上。
      (5)static并没有增加程序的时空开销,相反她还缩短了子类对父类静态成员的访问
时间,节省了子类的内存空间。
      (6)静态数据成员在<定义或说明>时前面加关键字static。
      (7)静态数据成员是静态存储的,所以必须对它进行初始化。
      (8)静态成员初始化与一般数据成员初始化不同:
      初始化在类体外进行,而前面不加static,以免与一般静态变量或对象相混淆;
      初始化时不加该成员的访问权限控制符private,public等;
           初始化时使用作用域运算符来标明它所属类;
           所以我们得出静态数据成员初始化的格式:
         <数据类型><类名>::<静态数据成员名>=<值>
      (9)为了防止父类的影响,可以在子类定义一个与父类相同的静态变量,以屏蔽父类的影响。这里有一点需要注意:我们说静态成员为父类和子类共享,但我们有重复定义了静态成员,这会不会引起错误呢?不会,我们的编译器采用了一种绝妙的手法:name-mangling 用以生成唯一的标志。

posted @ 2008-07-05 10:05 深邃者 阅读(159) | 评论 (0)编辑 收藏

WM_DESTROY 和 WM_NCDESTROY

WM_DESTROY 和 WM_NCDESTROY 消息之间有什么区别?

原文链接 What is the difference between WM_DESTROY and WM_NCDESTROY?

在窗口销毁时有两个紧密关联的 windows 消息, 就是 WM_DESTROY 和 WM_NCDESTROY. 它们有何区别?

区别就是 WM_DESTROY 消息是在窗口销毁动作序列中的开始被发送的, 而 WM_NCDESTROY 消息是在结尾. 这在你的窗口拥有子窗口时是个重大区别. 如果你有一个带子窗口的父窗口, 那么消息的发送序列 (在没有怪诞行为影响的前提下) 就像这样:

hwnd = parent, uMsg = WM_DESTROY
hwnd = child, uMsg = WM_DESTROY
hwnd = child, uMsg = WM_NCDESTROY
hwnd = parent, uMsg = WM_NCDESTROY

注意, 父窗口是在子窗口被销毁之前收到 WM_DESTROY 消息, 在子窗口被销毁之后收到 WM_NCDESTROY 消息.

两个销毁消息, 一个在开头, 一个在结尾, 这意味着, 对于你自己的模块, 你可以通过处理相应的消息来执行清理操作.
例如, 如果有些东西必须在开头清理, 那么你可以使用 WM_DESTROY 消息.

WM_NCDESTROY 消息是你窗口将会收到的最后一个消息 (在没有怪诞行为影响的前提下), 因此, 这里是做 "最终清理" 的最佳场所.
这就是为什么我们的 new scratch 程序会一直等到 WM_NCDESTROY 销毁它的实例变量, 才会返回.

与这两个销毁消息配对的, 是 WM_CREATE 和 WM_NCCREATE 这两个类似的消息. 与 WM_NCDESTROY 是你窗口收到的最后一条消息类似,
WM_NCCREATE 消息是第一条消息, 这是一个创建你自己的实例变量的好地方. 需要注意的是, 如果你导致 WM_NCCREATE 消息返回失败,
那么所有你将收到的消息就只有 WM_NCDESTROY 了; 不会有 WM_DESTROY 消息了, 因为你根本就没有收到相应的 WM_CREATE 消息.

那么什么是我一直在暗示的 "怪诞行为" 呢? 下一次 (When the normal window destruction messages are thrown for a loop) 我们再说

posted @ 2008-01-23 18:21 深邃者 阅读(350) | 评论 (0)编辑 收藏

悬挂指针

悬挂指针与boost::weak_ptr

   与内存泄露相比,C++最令人头痛的问题是内存越界,而内存越界很多情况下是由于悬挂指针引起的。  
  假设一个指针变量:
  Object * ptr;
  使用ptr时,我们除了要判断ptr是否为0以外,还要怀疑它指向的对象是否有效,是不是已经在别的地方被销毁了。我们希望当它指向的对象被销毁时,ptr被自动置为0。
  显然,C++没有这种机制,但是,可以借助于boost::weak_ptr做到这一点。

inline void null_deleter(void const *
{
}

class X
{
private:

    shared_ptr
<X> this_;
    
int i_;

public:

    
explicit X(int i): this_(this, &null_deleter), i_(i)
    {
    }

    X(X 
const & rhs): this_(this, &null_deleter), i_(rhs.i_)
    {
    }

    X 
& operator=(X const & rhs)
    {
        i_ 
= rhs.i_;
    }

    weak_ptr
<X> weak_this() const { return this_; }
};


 

定义变量:
weak_ptr<X> ptr = x.weak_this();  // x为一个X 对象 

则当 x 被销毁时,ptr 被自动置为无效。使用方法如下:

if ( shard_ptr<X>  safePtr  = ptr.lock() )  safePtr->do_something();

这种办法用于单线程中,因为 x  对象可能是基于栈分配的。如果需要在多线程中访问X对象,那么最好的办法还是使用shared_ptr 来管理对象的生命期。这样的话,对于safePtr, 可以保证在 safePtr 的生命期内,它所指向的对象不会被其它线程删除。

posted @ 2007-11-09 19:28 深邃者 阅读(531) | 评论 (0)编辑 收藏

CString

1. CString实现的机制.

CString是通过“引用”来管理串的,“引用”这个词我相信大家并不陌生,象Window内核对象、COM对象等都是通过引用来实现的。而CString也是通过这样的机制来管理分配的内存块。实际上CString对象只有一个指针成员变量,所以任何CString实例的长度只有4字节.

即: int len = sizeof(CString);//len等于4

这个指针指向一个相关的引用内存块,如图: CString str("abcd");

‘A’

‘B’

‘C’

‘D’

0

0x04040404 head部,为引用内存块相关信息

str 0x40404040

正因为如此,一个这样的内存块可被多个CString所引用,例如下列代码:

CString str("abcd");

CString a = str;

CString b(str);

CString c;

c = b;

上面代码的结果是:上面四个对象(str,a,b,c)中的成员变量指针有相同的值,都为0x40404040.而这块内存块怎么知道有多少个CString引用它呢?同样,它也会记录一些信息。如被引用数,串长度,分配内存长度。

这块引用内存块的结构定义如下:

struct CStringData

{

long nRefs; //表示有多少个CString 引用它. 4

int nDataLength; //串实际长度. 4

int nAllocLength; //总共分配的内存长度(不计这头部的12字节). 4

};

由于有了这些信息,CString就能正确地分配、管理、释放引用内存块。

如果你想在调试程序的时候获得这些信息。可以在Watch窗口键入下列表达式:

(CStringData*)((CStringData*)(this->m_pchData)-1)或

(CStringData*)((CStringData*)(str.m_pchData)-1)//str为指CString实例

正因为采用了这样的好机制,使得CString在大量拷贝时,不仅效率高,而且分配内存少。

2.LPCTSTR 与 GetBuffer(int nMinBufLength)

这两个函数提供了与标准C的兼容转换。在实际中使用频率很高,但却是最容易出错的地方。这两个函数实际上返回的都是指针,但它们有何区别呢?以及调用它们后,幕后是做了怎样的处理过程呢?

(1) LPCTSTR 它的执行过程其实很简单,只是返回引用内存块的串地址。 它是作为操作符重载提供的,所以在代码中有时可以隐式转换,而有时却需强制转制。如:

CString str;

const char* p = (LPCTSTR)str;

//假设有这样的一个函数,Test(const char* p); 你就可以这样调用

Test(str);//这里会隐式转换为LPCTSTR

(2) GetBuffer(int nMinBufLength) 它类似,也会返回一个指针,不过它有点差别,返回的是LPTSTR

(3) 这两者到底有何不同呢?我想告诉大家,其本质上完全不一样,一般说LPCTSTR转换后只应该当常量使用,或者做函数的入参;而GetBuffer(...)取出指针后,可以通过这个指针来修改里面的内容,或者做函数的出参。为什么呢?也许经常有这样的代码:

CString str("abcd");

char* p = (char*)(const char*)str;

p[2] = 'z';

其实,也许有这样的代码后,你的程序并没有错,而且程序也运行得挺好。但它却是非常危险的。再看

CString str("abcd");

CString test = str;

....

char* p = (char*)(const char*)str;

p[2] = 'z';

strcpy(p, "akfjaksjfakfakfakj");//这下完蛋了

你知道此时,test中的值是多少吗?答案是"abzd"。它也跟着改变了,这不是你所期望发生的。但为什么会这样呢?你稍微想想就会明白,前面说过,因为CString是指向引用块的,str与test指向同一块地方,当你p[2]='z'后,当然test也会随着改变。所以用它做LPCTSTR做转换后,你只能去读这块数据,千万别去改变它的内容。

假如我想直接通过指针去修改数据的话,那怎样办呢?就是用GetBuffer(...).看下述代码:

CString str("abcd");

CString test = str;

....

char* p = str.GetBuffer(20);

p[2] = 'z'; // 执行到此,现在test中值却仍是"abcd"

strcpy(p, "akfjaksjfakfakfakj"); // 执行到此,现在test中值还是"abcd"

为什么会这样?其实GetBuffer(20)调用时,它实际上另外建立了一块新内块存,并分配20字节长度的buffer,而原来的内存块引用计数也相应减1. 所以执行代码后str与test是指向了两块不同的地方,所以相安无事。

(4) 不过这里还有一点注意事项:就是str.GetBuffer(20)后,str的分配长度为20,即指针p它所指向的buffer只有20字节长,给它赋值时,切不可超过,否则灾难离你不远了;如果指定长度小于原来串长度,如GetBuffer(1),实际上它会分配4个字节长度(即原来串长度);另外,当调用GetBuffer(...)后并改变其内容,一定要记得调用ReleaseBuffer(),这个函数会根据串内容来更新引用内存块的头部信息。

(5) 最后还有一注意事项,看下述代码:

char* p = NULL;

const char* q = NULL;

{

CString str = "abcd";

q = (LPCTSTR)str;

p = str.GetBuffer(20);

AfxMessageBox(q);// 合法的

strcpy(p, "this is test");//合法的,

}

AfxMessageBox(q);// 非法的,可能完蛋

strcpy(p, "this is test");//非法的,可能完蛋

这里要说的就是,当返回这些指针后, 如果CString对象生命结束,这些指针也相应无效。

3.拷贝 & 赋值 & "引用内存块" 什么时候释放?

下面演示一段代码执行过程

void Test()

{

CString str("abcd");

//str指向一引用内存块(引用内存块的引用计数为1,长度为4,分配长度为4)

CString a;

//a指向一初始数据状态,

a = str;

//a与str指向同一引用内存块(引用内存块的引用计数为2,长度为4,分配长度为4)

CString b(a);

//a、b与str指向同一引用内存块(引用内存块的引用计数为3,长度为4,分配长度为4)

{

LPCTSTR temp = (LPCTSTR)a;

//temp指向引用内存块的串首地址。(引用内存块的引用计数为3,长度为4,分配长度为4)

CString d = a;

//a、b、d与str指向同一引用内存块(引用内存块的引用计数为4, 长度为4,分配长度为4)

b = "testa";

//这条语句实际是调用CString::operator=(CString&)函数。 b指向一新分配的引用内存块。(新分配的引用内存块的 引用计数为1, 长度为5, 分配长度为5)

//同时原引用内存块引用计数减1. a、d与str仍指向原 引用内存块(引用内存块的引用计数为3,长度为4,分配长度为4)

}

//由于d生命结束,调用析构函数,导至引用计数减1(引用内存块的引用计数为2,长度为4,分配长度为4)

LPTSTR temp = a.GetBuffer(10);

//此语句也会导致重新分配新内存块。temp指向新分配引用内存块的串首地址(新 分配的引用内存块的引用计数为1,长度为0,分配长度为10)

//同时原引用内存块引用计数减1. 只有str仍 指向原引用内存块 (引用内存块的引用计数为1, 长度为4, 分配长度为4)

strcpy(temp, "temp");

//a指向的引用内存块的引用计数为1,长度为0,分配长度为10 a.ReleaseBuffer();//注意:a指向的引用内存块的引用计数为1,长度为4,分配长度为10

}

//执行到此,所有的局部变量生命周期都已结束。对象str a b 各自调用自己的析构构

//函数,所指向的引用内存块也相应减1

//注意,str a b 所分别指向的引用内存块的计数均为0,这导致所分配的内存块释放

通过观察上面执行过程,我们会发现CString虽然可以多个对象指向同一引用内块存,但是它们在进行各种拷贝、赋值及改变串内容时,它的处理是很智能并且非常安全的,完全做到了互不干涉、互不影响。当然必须要求你的代码使用正确恰当,特别是实际使用中会有更复杂的情况,如做函数参数、引用、及有时需保存到CStringList当中,如果哪怕有一小块地方使用不当,其结果也会导致发生不可预知的错误

5 FreeExtra()的作用

看这段代码

(1) CString str("test");

(2) LPTSTR temp = str.GetBuffer(50);

(3) strcpy(temp, "there are 22 character");

(4) str.ReleaseBuffer();

(5) str.FreeExtra();

上面代码执行到第(4)行时,大家都知道str指向的引用内存块计数为1,长度为22,分配长度为50. 那么执行str.FreeExtra()时,它会释放所分配的多余的内存。(引用内存块计数为1,长度为22,分配长度为22)

6 Format(...) 与 FormatV(...)

这条语句在使用中是最容易出错的。因为它最富有技巧性,也相当灵活。在这里,我没打算对它细细分析,实际上sprintf(...)怎么用,它就怎么用。我只提醒使用时需注意一点:就是它的参数的特殊性,由于编译器在编译时并不能去校验格式串参数与对应的变元的类型及长度。所以你必须要注意,两者一定要对应上,

否则就会出错。如:

CString str;

int a = 12;

str.Format("first:%l, second: %s", a, "error");//result?试试

7 LockBuffer() 与 UnlockBuffer()

顾名思议,这两个函数的作用就是对引用内存块进行加锁及解锁。但使用它有什么作用及执行过它后对CString串有什么实质上的影响。其实挺简单,看下面代码:

(1) CString str("test");

(2) str.LockBuffer();

(3) CString temp = str;

(4) str.UnlockBuffer();

(5) str.LockBuffer();

(6) str = "error";

(7) str.ReleaseBuffer();

执行完(3)后,与通常情况下不同,temp与str并不指向同一引用内存块。你可以在watch窗口用这个表达式(CStringData*)((CStringData*)(str.m_pchData)-1)看看。

其实在msdn中有说明:

While in a locked state, the string is protected in two ways:

No other string can get a reference to the data in the locked string, even if that string is assigned to the locked string.

The locked string will never reference another string, even if that other string is copied to the locked string.

8 CString 只是处理串吗?

不对,CString不只是能操作串,而且还能处理内存块数据。功能完善吧!看这段代码

char p[20];

for(int loop=0; loop<sizeof(p); loop++)

{

p[loop] = 10-loop;

}

CString str((LPCTSTR)p, 20);

char temp[20];

memcpy(temp, str, str.GetLength());

str完全能够转载内存块p到内存块temp中。所以能用CString来处理二进制数据

8 AllocSysString()与SetSysString(BSTR*)

这两个函数提供了串与BSTR的转换。使用时须注意一点:当调用AllocSysString()后,须调用它SysFreeString(...)

9 参数的安全检验

在MFC中提供了多个宏来进行参数的安全检查,如:ASSERT. 其中在CString中也不例外,有许多这样的参数检验,其实这也说明了代码的安全性高,可有时我们会发现这很烦,也导致Debug与Release版本不一样,如有时程序Debug通正常,而Release则程序崩溃;而有时恰相反,Debug不行,Release行。其实我个人认为,我们对CString的使用过程中,应力求代码质量高,不能在Debug版本中出现任何断言框,哪怕release运行似乎看起来一切正常。但很不安全。如下代码:

(1) CString str("test");

(2) str.LockBuffer();

(3) LPTSTR temp = str.GetBuffer(10);

(4) strcpy(temp, "error");

(5) str.ReleaseBuffer();

(6) str.ReleaseBuffer();//执行到此时,Debug版本会弹出错框

10 CString的异常处理

我只想强调一点:只有分配内存时,才有可能导致抛出CMemoryException.

同样,在msdn中的函数声明中,注有throw( CMemoryException)的函数都有重新分配或调整内存的可能。

11 跨模块时的Cstring。即一个DLL的接口函数中的参数为CString&时,它会发生怎样的现象。解答我遇到的问题。我的问题原来已经发贴,地址为:

http://www.csdn.net/expert/topic/741/741921.xml?temp=.2283136

构造一个这样CString对象时,如CString str,你可知道此时的str所指向的引用内存块吗?也许你会认为它指向NULL。其实不对,如果这样的话,CString所采用的引用机制管理内存块就会有麻烦了,所以CString在构造一个空串的对象时,它会指向一个固定的初始化地址,这块数据的声明如下:

AFX_STATIC_DATA int _afxInitData[] = {-1,0,0,0};

简要描述概括一下:当某个CString对象串置空的话,如Empty(),CString a等,它的成员变量m_pchData就会指向_afxInitData这个变量的地址。当这个CString对象生命周期结束时,正常情况下它会去对所指向的引用内存块计数减1,如果引用计数为0(即没有任何CString引用它时),则释放这块引用内存。而现在的情况是如果CString所指向的引用内存块是初始化内存块时,则不会释放任何内存。

说了这么多,这与我遇到的问题有什么关系呢?其实关系大着呢?其真正原因就是如果exe模块与dll模块有一个是static编译连接的话。那么这个CString初始化数据在exe模块与dll模块中有不同的地址,因为static连接则会在本模块中有一份源代码的拷贝。另外一种情况,如果两个模块都是share连接的,CString的实现代码则在另一个单独的dll中实现,而AFX_STATIC_DATA指定变量只装一次,所以两个模块中_afxInitData有相同的地址。

现在问题完全明白了吧!你可以自己去演示一下。

__declspec (dllexport) void test(CString& str)

{

str = "abdefakdfj";//如果是static连接,并且传入的str为空串的话,这里出错。

}

posted @ 2007-07-24 17:11 深邃者 阅读(565) | 评论 (0)编辑 收藏

指针

指针高级--<高质量编程>

//  execise2.cpp : Defines the entry point for the console application.
//

#include 
" stdafx.h "
#include 
< iostream >
using   namespace  std;
// -----------------------------------------------
void  GetMemory1( char   * p)
{
    p 
=  ( char   * )malloc( 100 );
}

void  Test1( void )
{
    
char   * str  =  NULL;
    GetMemory1( str );
    strcpy(str, 
" hello world " );
    printf(str);
}

// -----------------------------------------------
char   * GetMemory2( void )
{   
    
char  p[]  =   " hello world " ;
    
return  p;
}

void  Test2( void )
{
    
char   * str  =  NULL;
    str 
=  GetMemory2();  
    printf(str);
}

// -----------------------------------------------
void  GetMemory3( char   ** p,  int  num)
{
    
* =  ( char   * )malloc(num);
}

void  Test3( void )
{
    
char   * str  =  NULL;
    GetMemory3(
& str,  100 );
    strcpy(str, 
" hello " );  
    printf(str);    
}

// -----------------------------------------------
void  Test4( void )
{
    
char   * str  =  ( char   * ) malloc( 100 );
    strcpy(str, 
" hello " );
    free(str);      
    
if (str  !=  NULL)
    
{
        strcpy(str, 
" world " ); 
        printf(str);
    }

}

// -----------------------------------------------
main()
{
    
// -----------------------------------------------
    
// 请问运行Test1函数会有什么样的结果?
    
//
    
// 答:程序崩溃。
    
//
    
// 因为GetMemory并不能传递动态内存,
    
//
    
// Test函数中的 str一直都是 NULL。
    
//
    
// strcpy(str, "hello world");将使程序崩溃。
    Test1();
    
// -----------------------------------------------
    
//     请问运行Test2函数会有什么样的结果?
    
//
    
// 答:可能是乱码。
    
//
    
// 因为GetMemory返回的是指向“栈内存”的指针,
    
// 该指针的地址不是 NULL,但其原现的内容已经被清除,新内容不可知。
    Test2();
    
// -----------------------------------------------
    
//     请问运行Test3函数会有什么样的结果?
    
//
    
// 答:
    
//
    
// (1)能够输出hello
    
//
    
// (2)内存泄漏
    Test3();
    
// -----------------------------------------------
    
//     请问运行Test函数会有什么样的结果?
    
//
    
// 答:篡改动态内存区的内容,后果难以预料,非常危险。
    
//
    
// 因为free(str);之后,str成为野指针,
    
//
    
// if(str != NULL)语句不起作用。
    Test4();
    
// -----------------------------------------------
}

posted @ 2007-07-24 16:48 深邃者 阅读(104) | 评论 (0)编辑 收藏

调试Release版本

很多时候程序的 Debug 版本运行没有任何问题,但是一旦发布 Release 版本后,运行就出错,着实让人郁闷。大家知道,VC++ 中 Release 版本是对无法对源代码进行调试的。一般的做法是在怀疑有错误的代码前后插入MessageBox 函数,在函数中显示可能导致错误的变量的值。或者插入写文件的语句,输出可能导致错误的变量的值到一个记录文件。其实,除了上面讲的这个办法之外,还有其它的途径来调试 Release 版本的。下面就结合自己的经验和网上查找的一些资料给出调试 Release 版本的两个方法:

方法一、利用 *.PDB 符号文件调试 Release 版本
在 VCKBASE 的在线杂志中有一篇参考文章:符号文件——Windows 应用程序调试必备(http://www.vckbase.com/document/viewdoc/?id=1710),文章谈到了如何产生 Release 版本二进制文件对应的 PDB 文件的问题。有了 PDB 文件后,就可以调试 Release 了,方法是:
    1、在Project Settings里选Settings For为All Configurations。
    2、在C/C++标签中,Debug info 选 Program Database。
    3、在Link 标签中,Category选 Debug,选中Debug info 复选框和Microsoft format。
进行了上述设置后,我们就可以像在调试版本中那样设置断点进行测试了,由于代码优化,有些变量观察不到,行的运行顺序可能也会不同。
有一点需要注意:ASSERT宏在 Release 版本中不起作用,在 Release 版本中应该使用 VERIFY 来代替 ASSERT 进行调试。如果发行版本运行有问题,可以先禁止所有代码优化再进行调试。

方法二、在需要加断点的地方添加如下汇编语句:
    __asm int 3

不过调试的时候无法显示C程序,只有asm代码。
    
此处 int 3 是专门用来设置断点的,是 CPU 定义的,Windows 和 DOS 下的大多数调试器都采用这种方法。

posted @ 2007-07-24 16:31 深邃者 阅读(222) | 评论 (0)编辑 收藏

LINK错误

vc编译链接错误--LNK2001,LNK2019,

--  LINK2001
学习VC++时经常会遇到链接错误LNK2001,该错误非常讨厌,因为对于
编程者来说,最好改的错误莫过于编译错误,而一般说来发生连接错误时,
编译都已通过。产生连接错误的原因非常多,尤其LNK2001错误,常常使人不
明其所以然。如果不深入地学习和理解VC++,要想改正连接错误LNK2001非
常困难。
  初学者在学习VC++的过程中,遇到的LNK2001错误的错误消息主要为:
  unresolved external symbol “symbol”(不确定的外部“符号”)。
  如果连接程序不能在所有的库和目标文件内找到所引用的函数、变量或
标签,将产生此错误消息。一般来说,发生错误的原因有两个:一是所引用
的函数、变量不存在、拼写不正确或者使用错误;其次可能使用了不同版本
的连接库。
  以下是可能产生LNK2001错误的原因:
  一.由于编码错误导致的LNK2001。
  1.不相匹配的程序代码或模块定义(.DEF)文件能导致LNK2001。例如,
如果在C++ 源文件内声明了一变量“var1”,却试图在另一文件内以变量
“VAR1”访问该变量,将发生该错误。
  2.如果使用的内联函数是在.CPP文件内定义的,而不是在头文件内定
义将导致LNK2001错误。
  3.调用函数时如果所用的参数类型同函数声明时的类型不符将会产生
LNK2001。
  4.试图从基类的构造函数或析构函数中调用虚拟函数时将会导致LNK2001。
  5.要注意函数和变量的可公用性,只有全局变量、函数是可公用的。
  静态函数和静态变量具有相同的使用范围限制。当试图从文件外部访问
任何没有在该文件内声明的静态变量时将导致编译错误或LNK2001。
  函数内声明的变量(局部变量) 只能在该函数的范围内使用。
  C++ 的全局常量只有静态连接性能。这不同于C,如果试图在C++的
多个文件内使用全局变量也会产生LNK2001错误。一种解决的方法是需要时在
头文件中加入该常量的初始化代码,并在.CPP文件中包含该头文件;另一种
方法是使用时给该变量赋以常数。
  二.由于编译和链接的设置而造成的LNK2001
  1.如果编译时使用的是/NOD(/NODEFAULTLIB)选项,程序所需要的运行
库和MFC库在连接时由编译器写入目标文件模块, 但除非在文件中明确包含
这些库名,否则这些库不会被链接进工程文件。在这种情况下使用/NOD将导
致错误LNK2001。
  2.如果没有为wWinMainCRTStartup设定程序入口,在使用Unicode和MFC
时将得到“unresolved external on _WinMain@16”的LNK2001错误信息。
  3.使用/MD选项编译时,既然所有的运行库都被保留在动态链接库之内,
源文件中对“func”的引用,在目标文件里即对“__imp__func” 的引用。
如果试图使用静态库LIBC.LIB或LIBCMT.LIB进行连接,将在__imp__func上发
生LNK2001;如果不使用/MD选项编译,在使用MSVCxx.LIB连接时也会发生LNK2001。
  4.使用/ML选项编译时,如用LIBCMT.LIB链接会在_errno上发生LNK2001。
  5.当编译调试版的应用程序时,如果采用发行版模态库进行连接也会产
生LNK2001;同样,使用调试版模态库连接发行版应用程序时也会产生相同的
问题。
  6.不同版本的库和编译器的混合使用也能产生问题,因为新版的库里可
能包含早先的版本没有的符号和说明。
  7.在不同的模块使用内联和非内联的编译选项能够导致LNK2001。如果
创建C++库时打开了函数内联(/Ob1或/Ob2),但是在描述该函数的相应头
文件里却关闭了函数内联(没有inline关键字),这时将得到该错误信息。
为避免该问题的发生,应该在相应的头文件中用inline关键字标志内联函数。
  8.不正确的/SUBSYSTEM或/ENTRY设置也能导致LNK2001。
  其实,产生LNK2001的原因还有很多,以上的原因只是一部分而已,对初
学者来说这些就够理解一阵子了。但是,分析错误原因的目的是为了避免错
误的发生。LNK2001错误虽然比较困难,但是只要注意到了上述问题,还是能
够避免和予以解决的。

LNK2019
函数只有申明,没有实现时,或是DLL中的东东没有export啊

posted @ 2007-07-24 16:29 深邃者 阅读(193) | 评论 (0)编辑 收藏

内存检测

1)工程是MFC工程,或是工程的设置中有Use MFC in a Shared DLL,
2)很多地方说是要定义以下宏
#ifdef _DEBUG
#define new DEBUG_NEW
#endif
但是我发现只要include <afx.h> 即可。(大家可以检测一下)
3)可以在F5运行程序后,在output窗口中看到如下的内存泄露的显示。(只在debug下有用哦)
4)如果有泄露,则显示如下:
Detected memory leaks!
Dumping objects ->
{214} normal block at 0x00D91618, 4 bytes long.
 Data: <    > 00 00 00 00
{208} normal block at 0x00D914D0, 4 bytes long.
 Data: <    > 00 00 00 00
{207} normal block at 0x00D91490, 4 bytes long.
 Data: <    > D0 14 D9 00
{205} normal block at 0x00D91410, 4 bytes long.
 Data: <    > 00 00 00 00
{204} normal block at 0x003AFFD8, 4 bytes long.
 Data: <    > 10 14 D9 00
{203} normal block at 0x003AFF98, 4 bytes long.
 Data: <    > 00 00 00 00
{202} normal block at 0x003AFF58, 4 bytes long.
 Data: <  : > 98 FF 3A 00
{200} normal block at 0x003AFF18, 4 bytes long.
 Data: <    > 00 00 00 00
Object dump complete.

posted @ 2007-07-24 15:08 深邃者 阅读(164) | 评论 (0)编辑 收藏

仅列出标题
共5页: 1 2 3 4 5