设为首页 加入收藏

TOP

6.1.4 全局/静态变量
2013-10-07 13:20:17 来源: 作者: 【 】 浏览:61
Tags:6.1.4 全局 静态 变量

6.1.4  全局/静态变量

首先列出规则如下:

不能定义类的全局或者静态对象,除非这个类没有构造函数;否则全局对象将因初始化过程中含有无法解决的符号,而导致链接失败。

读者可能难以理解这个规定,所以要用实例进行更深的挖掘才行。以simClass的clsInt类为例,如果定义如下全局变量:

  1. clsInt  gA; 

对项目进行编译,会毫不留情地得到如下错误(也是链接错误):

  1. errors in directory c:\trunk\simclass  
  2. c:\trunk\simclass\main.obj : error LNK2019:
    unresolved external symbol _atexit referenced 
    in function "void __cdecl 'dynamic initializer
    for 'gA''(void)" ( __EgA@@YAXXZ) 

上面的链接错误,是由于函数 __EgA@@YAXXZ中找不到符号_atexit。这两个名字都怪得不得了!理解它们要从C++(www.cppentry.com)标准说起,C++(www.cppentry.com)标准规定对于全局对象的处理,编译器要保证全局对象在main()函数运行之前已经被初始化,并且保证main()函数在退出前被删除(析构)。变量的初始化与删除,需要编译器专门为它们各自创建一个函数,并在合适的时机进行调用。函数名称根据不同的编译器会有所不同,在这里看到,用于对gA进行初始化的是函数 __EgA@@YAXXZ,笔者通过IAD反汇编后看到,用于删除(析构)的是函数 __FgA@@YAXXZ。后者一点问题都没有,但前者遇到了问题,无法解析_atexit符号。笔者将其汇编代码拷贝如下:

  1. // 函数名,注释很明白地告诉我们,此函数是gA的初始化函数  
  2. __EgA@@YAXXZ:      ; DATA XREF: .CRT$XCU:_gA$initializer$o  
  3. 0000031E   mov     edi, edi  
  4. 00000320   push    ebp  
  5. 00000321   mov     ebp, esp  
  6.  
  7. // 下面首先会调用clsInt的默认构造函数  
  8. // 第一句是将m_nValue赋值为0  
  9. 00000323   mov     ds:clsInt gA, 0  
  10.  
  11. // 下面是DbgPrint调用  
  12. 0000032D   mov     eax, ds:clsInt gA  
  13. 00000332   push    eax  
  14. 00000333   push    offset clsInt gA  
  15. 00000338   push    offset PrintString  
  16. 0000033D   call    _DbgPrint  
  17. 0000033D  
  18. 00000342   add     esp, 0Ch  
  19.  
  20. // 初始化已经完毕了,问题出在这里  
  21. //初始化完毕后,把 __FgA@@YAXXZ地址作为参数,
    调用_atexit以注册终止函数  
  22. 00000345   push    offset  __FgA@@YAXXZ  
  23. 0000034A   call    _atexit  
  24. 0000034A  
  25.  
  26. // 恢复堆栈  
  27. 0000034F   add     esp, 4  
  28. 00000352   pop     ebp  
  29. 00000353   retn  
  30. 00000353  
  31. 00000353 _text$yc        ends 

上面的汇编代码,大部分都是正确的,只是到了最后调用_atexit函数时才出了错(_atexit是导入符号,实际函数名应去掉前面的"_",即atexit)。atexit是一个C标准函数,其作用是向系统注册终止函数,即主程序在终止之前需调用的处理函数。上面我们看到,atexit将 __FgA@@YAXXZ作为参数进行了调用以析构gA。在逻辑上是没有问题的,但atexit函数在内核中未实现。实际上,它有下面的一行调用:

  1. atexit( __FgA@@YAXXZ); 

现在的问题就归结为:内核中没有C运行时函数atexit。请问:它可以有吗?它难道不可以有吗?

上面笔者也说过,内核代码和用户程序是非常不一样的。用户程序的生命周期由main()调用开始,main()调用结束,整个程序也即完结。而驱动程序却不一样,虽然我们有时候把DriverEntry比作main(),但二者在本质上不同,DriverEntry的生命周期非常短,其作用仅是将内核文件镜像加载到系统中时进行驱动初始化,调用结束后驱动程序的其他部分依旧存在,并不随它而终止。所以我们一般可把DriverEntry称为"入口函数",而不可称为"主函数"。因此作为内核驱动来说,它没有一个明确的退出点,这应该是atexit无法在内核中实现的原因吧。

从图6-2我们看到,用户程序是一个独立运行单位,main()函数是主线程,它的生命周期也就是程序的生命周期。而内核驱动呢?它的生命周期其实只是镜像文件的生命周期,即加载与卸载,并没有固定的主线程与之匹配甚至支配其生命周期;相反,驱动代码可以出现在任何线程环境中,被任何线程调用。

话说回来,其实驱动程序也是有明显的生命周期的,即从DriverEntry开始到DriverUnload结束的镜像文件的生命周期,如图6-3所示。这也并非不可利用,笔者觉得,如果在DriverEntry调用前执行全局对象的初始化函数,而同时把终止函数注册到DriverUnload中,或许能够解决问题,但前提是要求系统要做相应的改动了。因为DriverUnload是可选的,所以若采用这种方法,应采取措施为未提供DriverUnload函数的驱动设置默认的卸载函数。但随着微软对这方面研究的深入,笔者相信,这个问题一定是他们的问题列表中必须解决的一项。

 
(点击查看大图)图6-2  用户程序
 
(点击查看大图)图6-3  内核假想实现

本节内容代码,请参看本书simClass示例工程。

内核中使用C++(www.cppentry.com)还有一点需要注意,就是C++(www.cppentry.com)编译器会在不提醒的情况下,使用堆栈生成临时变量若干,而内核堆栈是非常有限的,所以常常需要对此保持一份警惕。

】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
分享到: 
上一篇6.1 驱动中的类 下一篇6.3.1 基类、子类

评论

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