论坛交流
首页办公自动化| 网页制作| 平面设计| 动画制作| 数据库开发| 程序设计| 全部视频教程
应用视频: Windows | Word2007 | Excel2007 | PowerPoint2007 | Dreamweaver 8 | Fireworks 8 | Flash 8 | Photoshop cs | CorelDraw 12
编程视频: C语言视频教程 | HTML | Div+Css布局 | Javascript | Access数据库 | Asp | Sql Server数据库Asp.net  | Flash AS
当前位置 > 文字教程 > C语言程序设计教程
Tag:新手,函数,指针,数据类型,对象,Turbo,入门,运算符,数组,结构,二级,,tc,游戏,试题,问答,编译,视频教程

野指针安全论_VC技术_C语言教程

文章类别:C语言程序设计 | 发表日期:2010-6-19 13:47:54

野指针安全论_VC技术_C语言教程

本文来自21视频教程网C语言频道

 首先请诸位看以下一段“危险”的C++代码:
  void function(void)
  {
   char *str = new char[100];
   delete[] str;
   // Do something
   strcpy(str, "Dangerous!!");
  }
  之所以说其危险,是因为这是一段完全合乎语法的代码,编译的时候完美得一点错误也不会有,然而当运行到strcpy一句的时候,问题就会出现,因为在这之前,str的空间已经被delete掉了,所以strcpy当然不会成功。对于这种类似的情况,在林锐博士的书中有过介绍,称其为“野指针”。
  那么,诸位有没有见过安全的“野指针”呢?下面请看我的一段C++程序,灵感来自CSDN上的一次讨论。在此,我只需要C++的“类”,C++的其余一概不需要,因此我没有使用任何的C++标准库,连输出都是用printf完成的。
  #include <stdio.h>
  class CTestClass
  {
  public:
   CTestClass(void);
   int m_nInteger;
   void Function(void);
  };
  CTestClass::CTestClass(void)
  {
   m_nInteger = 0;
  }
  void CTestClass::Function(void)
  {
   printf("This is a test function.\n");
  }
  void main(void)
  {
   CTestClass *p = new CTestClass;
   delete p;
   p->Function();
  }
  OK,程序到此为止,诸位可以编译运行一下看看结果如何。你也许会惊异地发现:没有任何的出错信息,屏幕上竟然乖乖地出现了这么一行字符串:
  This is a test function.
  奇怪吗?不要急,还有更奇怪的呢,你可以把主函数中加上一句更不可理喻的:
  ((CTestClass*)NULL)->Function();
  这仍然没有问题!!
  我这还有呢,哈哈。现在你在主函数中这么写,倘说上一句不可理喻,那么以下可以叫做无法无天了:
  int i = 888;
  CTestClass *p2 = (CTestClass*)&i;
  p2->Function();
  你看到了什么?是的,“This is a test function.”如约而至,没有任何的错误。
  你也许要问为什么,但是在我解答你之前,请你在主函数中加入如下代码:
  printf("%d, %d", sizeof(CTestClass), sizeof(int));
  这时你就会看到真相了:输出结果是——得到的两个十进制数相等。对,由sizeof得到的CTestClass的大小其实就是它的成员m_nInteger的大小。亦即是说,对于CTestClass的一个实例化的对象(设为a)而言,只有a.m_nInteger是属于a这个对象的,而a.Function()却是属于CTestClass这个类的。所以以上看似危险的操作其实都是可行且无误的。
  现在你明白为什么我的“野指针”是安全的了,那么以下我所列出的,就是在什么情况下,我的“野指针”不安全:
  1、在成员函数Function中对成员变量m_nInteger进行操作;
  2、将成员函数Function声明为虚函数(virtual)。
  以上的两种情况,目的就是强迫野指针使用属于自己的东西导致不安全,比如第一种情况中操作本身的m_nInteger,第二种情况中变为虚函数的Function成为了属于对象的函数(这一点可以从sizeof看出来)。
  其实,安全的野指针在实际的程序设计中是几乎毫无用处的。我写这一篇文章,意图并不是像孔乙己一样去琢磨回字有几种写法,而是想通过这个小例子向诸位写明白C++的对象实例化本质,希望大家不但要明白what和how,更要明白why。李马二零零三年二月二十日作于自宅。

关于成员函数CTestClass::Function的补充说明

  这个函数是一个普通的成员函数,它在编译器的处理下,会成为类似如下的代码:
  void Function(const CTestClass * this) // ①
  {
   printf("This is a test function.\n");
  }
  那么p->Function();一句将被编译器解释为:
  Function(p);
  这就是说,普通的成员函数必须经由一个对象来调用(经由this指针激活②)。那么由上例的delete之后,p指针将会指向一个无效的地址,然而p本身是一个有效的变量,因此编译能够通过。并且在编译通过之后,由于CTestClass::Function的函数体内并未对这个传入的this指针进行任何的操作,所以在这里,“野指针”便成了一个看似安全的东西。
  然而若这样改写CTestClass::Function:
  void CTestClass::Function(void)
  {
   m_nInteger = 0;
  }
  那么它将会被编译器解释为:
  void Function(const CTestClass * this)
  {
   this->m_nInteger = 0;
  }
  你看到了,在p->Function();的时候,系统将会尝试在传入的这个无效地址中寻找m_nInteger成员并将其赋值为0,剩下的我不用说了——非法操作出现了。
  至于virtual虚函数,如果在类定义之中将CTestClass声明为虚函数:
  class CTestClass
  {
  public:
   // ...
   virtual void Function(void);
  }
  那么C++在构建CTestClass类的对象模型时,将会为之分配一个虚函数表vptr(可以从sizeof看出来)。vptr是一个指针,它指向一个函数指针的数组,数组中的成员即是在CTestClass中声明的所有虚函数。在调用虚函数的时候,必须经由这个vptr,这也就是为什么虚函数较之普通成员函数要消耗一些成本的缘故。以本例而言,p->Function();一句将被编译器解释为:
  (*p->vptr[1])(p); // 调用vptr表中索引号为1的函数(即Function)③
  上面的代码已经说明了,如果p指向一个无效的地址,那么必然会有非法操作。
备注:
  ①关于函数的命名,我采用了原名而没有变化。事实上编译器为了避免函数重载造成的重名情况,会对函数的名字进行处理,使之成为独一无二的名称。
  ②将成员函数声明为static,可以使成员函数不经由this指针便可调用。
  ③vptr表中,索引号0为类的type_info。

视频教程列表
文章教程搜索
 
C语言程序设计推荐教程
C语言程序设计热门教程
看全部视频教程
购买方式/价格
购买视频教程: 咨询客服
tel:15972130058