6.1.2 new/delete
查看上面的代码,会发现一个不同于以往的new操作符。这是怎么回事呢?我们这一节就讲讲它。在用户程序中,创建和释放一个对象使用 new/delete方法,其底层乃是调用HeapAllocate/HeapFree 堆API从线程堆栈中申请空间。但问题是,内核CRT没有提供new/delete操作符,所以需要自己定义。自定义的new/delete操作符,自然也是能够从堆栈中分配内存的,内核中有RtlAllocateHeap/RtlFreeHeap堆栈服务函数。但在内核中,我们一般使用内存池来获取内存,实际上内存池和堆栈使用了同一套实现机制。使用ExAllocatePool/ExFreePool函数对从内存池申请/释放内存,下面是一个例子。
- __forceinline
- void* __cdecl operator new(size_t size,
- POOL_TYPE pool_type,
- ULONG pool_tag)
- {
- ASSERT((pool_type < MaxPoolType) && (0 != size));
- if(size == 0)return NULL;
-
- // 中断级检查。分发级别和以上的级别只能分配非分页内存
- ASSERT(pool_type == NonPagedPool ||
- (KeGetCurrentIrql() < DISPATCH_LEVEL));
-
- return ExAllocatePoolWithTag(pool_type,
- static_cast<ULONG>(size),
- pool_tag);
- }
上面的函数定义有几个细节的地方应当注意一下。首先注意new操作符重载,它的第一个参数一定是size_t,用来表示将分配缓冲区的长度;其次注意分页内存和非分页内存的区别,即pool_type所表示者,在DISPATCH_LEVEL及以上的级别是不能分配分页内存的。
下面是使用new进行内存申请的一个例子。
- // 定义一个32位的TAG值
- #define TAG 'abcd'
- // 外部已经定义了一个clsName类
- extern class clsName;
-
- // 为clsName申请对象空间
- clsName* objName = NULL;
- objName = new(NonPagedPool, TAG)clsName();
上面的new操作和用户程序中的new操作具有同样的功效,但需要注意第一个参数size_t是必须外置的,编译器会自动用sizeof(clsName)求取长度并作为第一个参数。一般地说,对于类似下面的语句:
- className objName = new(…) className(…)
其执行过程是,首先由new操作符为新对象动态分配内存,并返回指针;然后再对此新创建的对象,选择与className(…) 相符的构造函数进行初始化。
再来看看delete操作符的重载。
- __forceinline
- void __cdecl operator delete(void* pointer)
- {
- ASSERT(NULL != pointer);
- if (NULL != pointer)
- ExFreePool(pointer);
- }
删除对象数组,即delete[]操作符重载。
- __forceinline
- void __cdecl operator delete[](void* pointer)
- {
- ASSERT(NULL != pointer);
- if (NULL != pointer)
- ExFreePool(pointer);
- }
上面两个函数最终都会将指定地址的内存释放,但在释放之前,前者会调用指定对象的析构函数,后者会对数组中每个成员调用析构函数。示例如下:
- extern clsName *objName;
- extern clsName *objArray[];
- delete objName;
- delete[] objArray;
- 6.1.3 extern "C"
对extern "C"编译指令,大家不会感到陌生。它一般这样用:
- extern "C"{
- //…内容
- }
既然是编译指令,就一定是作用于编译时刻的。它告诉编译器,对于作用范围内的代码,以C编译器方式编译。一般是针对C++(www.cppentry.com)/Java等程序而用的。如果括号内仅有一项,那么括号可以省略。
最早让我们见识到它的作用的是在入口函数DriverEntry中。现在必须这样声明它:
- extern "C" NTSTATUS DriverEntry(
- IN PDRIVER_OBJECT DriverObject,
- IN PUNICODE_STRING RegistryPath
- );
初学者未必知道这一点,如果"忘记"做上述改动,将得到如下错误:
- error LNK2019: unresolved external symbol _DriverEntry@8
- referenced in function _GsDriverEntry@8
- 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",链接器即可正确地识别出符号了。