设为首页 加入收藏

TOP

6.1.2 new/delete
2013-10-07 13:20:26 来源: 作者: 【 】 浏览:60
Tags:6.1.2 new/delete

6.1.2  new/delete

查看上面的代码,会发现一个不同于以往的new操作符。这是怎么回事呢?我们这一节就讲讲它。在用户程序中,创建和释放一个对象使用 new/delete方法,其底层乃是调用HeapAllocate/HeapFree 堆API从线程堆栈中申请空间。但问题是,内核CRT没有提供new/delete操作符,所以需要自己定义。自定义的new/delete操作符,自然也是能够从堆栈中分配内存的,内核中有RtlAllocateHeap/RtlFreeHeap堆栈服务函数。但在内核中,我们一般使用内存池来获取内存,实际上内存池和堆栈使用了同一套实现机制。使用ExAllocatePool/ExFreePool函数对从内存池申请/释放内存,下面是一个例子。

  1. __forceinline  
  2. void* __cdecl operator new(size_t size,  
  3.                                 POOL_TYPE pool_type,  
  4.                                 ULONG pool_tag)  
  5. {  
  6.   ASSERT((pool_type < MaxPoolType) && (0 != size));  
  7.   if(size == 0)return NULL;  
  8.  
  9.   // 中断级检查。分发级别和以上的级别只能分配非分页内存  
  10.   ASSERT(pool_type ==  NonPagedPool ||  
  11.     (KeGetCurrentIrql() < DISPATCH_LEVEL));  
  12.  
  13.   return ExAllocatePoolWithTag(pool_type,  
  14.                               static_cast<ULONG>(size),  
  15.                               pool_tag);  

上面的函数定义有几个细节的地方应当注意一下。首先注意new操作符重载,它的第一个参数一定是size_t,用来表示将分配缓冲区的长度;其次注意分页内存和非分页内存的区别,即pool_type所表示者,在DISPATCH_LEVEL及以上的级别是不能分配分页内存的。

下面是使用new进行内存申请的一个例子。

  1. // 定义一个32位的TAG值  
  2. #define  TAG  'abcd'  
  3. // 外部已经定义了一个clsName类  
  4. extern  class  clsName;  
  5.  
  6. // 为clsName申请对象空间  
  7. clsName*  objName = NULL;  
  8. objName = new(NonPagedPool, TAG)clsName(); 

上面的new操作和用户程序中的new操作具有同样的功效,但需要注意第一个参数size_t是必须外置的,编译器会自动用sizeof(clsName)求取长度并作为第一个参数。一般地说,对于类似下面的语句:

  1. className  objName = new(…)  className(…) 

其执行过程是,首先由new操作符为新对象动态分配内存,并返回指针;然后再对此新创建的对象,选择与className(…) 相符的构造函数进行初始化。

再来看看delete操作符的重载。

  1. __forceinline  
  2. void __cdecl operator delete(void* pointer)  
  3. {  
  4.   ASSERT(NULL != pointer);  
  5.   if (NULL != pointer)  
  6.     ExFreePool(pointer);  

删除对象数组,即delete[]操作符重载。

  1. __forceinline  
  2. void __cdecl operator delete[](void* pointer)  
  3. {  
  4.   ASSERT(NULL != pointer);  
  5.   if (NULL != pointer)  
  6.     ExFreePool(pointer);  

上面两个函数最终都会将指定地址的内存释放,但在释放之前,前者会调用指定对象的析构函数,后者会对数组中每个成员调用析构函数。示例如下:

  1. extern  clsName  *objName;  
  2. extern  clsName  *objArray[];  
  3. delete  objName;  
  4. delete[]  objArray;  
  5. 6.1.3  extern "C" 

对extern "C"编译指令,大家不会感到陌生。它一般这样用:

  1. extern "C"{  
  2. //…内容  

既然是编译指令,就一定是作用于编译时刻的。它告诉编译器,对于作用范围内的代码,以C编译器方式编译。一般是针对C++(www.cppentry.com)/Java等程序而用的。如果括号内仅有一项,那么括号可以省略。

最早让我们见识到它的作用的是在入口函数DriverEntry中。现在必须这样声明它:

  1. extern "C" NTSTATUS DriverEntry(  
  2.                             IN PDRIVER_OBJECT  DriverObject,  
  3.                             IN PUNICODE_STRING  RegistryPath  
  4.                             ); 

初学者未必知道这一点,如果"忘记"做上述改动,将得到如下错误:

  1. error LNK2019: unresolved external symbol _DriverEntry@8  
  2.  referenced in function _GsDriverEntry@8  
  3. error LNK1120: 1 unresolved externals 

很奇怪,这是一个链接错误,说明编译过程是通过的。怎么回事呢?认真看一下错误内容,原来是系统在链接时找不到入口函数_DriverEntry@8。这个奇怪的函数名,很显然是C编译器对DriverEntry进行编译后的结果,前缀"_"是C编译器特有的,后缀"@8"是所有参数的长度。原来我们现在使用的是C++(www.cppentry.com)编译器,一定是它把DriverEntry编译成了系统无法认识的另一副模样了(实际上,C++(www.cppentry.com)编译器会把它编译成以" DriverEntry@@"开头的一串很长的符号)。

一旦加上extern "C"修饰符,上述问题即立刻消失了。extern "C"提醒编译器要使用C编译格式编译DriverEntry函数,这样编译生成的函数名称为"_DriverEntry@8",链接器即可正确地识别出符号了。

】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
分享到: 
上一篇6.2.3 KMDF驱动实现 下一篇6.1.1 一个简单的例子

评论

帐  号: 密码: (新用户注册)
验 证 码:
表  情:
内  容: