chaosuper85

C++博客 首页 新随笔 联系 聚合 管理
  118 Posts :: 0 Stories :: 3 Comments :: 0 Trackbacks

#

开发辅助工具大收集

除了我们日常开发使用的Visual C++、Delphi、JBuilder等等大家伙,
还有很多小巧好用的开发辅助工具,善用它们可以极大的提高我们的效率。

日常工作中我主要使用Visual C++开发程序,工作几年,收集了一些小工具,
下面逐个介绍给大家。也希望大家把自己的工具拿出来和大家分享。

如果大家需要,请跟贴,需要的朋友多的话,我上传到FTP上(都是没有限制的最新版本哟)

Visual C++插件

[1] Visual Assist

http://www.wholetomato.com/download/index.shtml

这是我现在使用最为频繁的工具,有了它,现在在Visual C++中写程序简直成了一种享受,
Visual Assist的智能提示功能实在是太强大了,估计大家都应该装了吧!
唯一不太爽的是Visual Assist对C++ STL和Templates的解析还是有待改进。

[2] WndTab

http://www.wndtabs.com

Visual C++ 6.0的编辑窗口没有分页显示,
想在打开的多个文件中切换非常麻烦,
WndTab为VC的编辑窗口加上了Tab,
现在点击每个文件的Tab就可以方便的切换到该文件进行编辑了,强烈推荐。

[3] BoundsCheck

CompuWare的调试工具,可以集成到Visual C++中。
BoundsCheck可以帮助我们发现程序中隐藏的bug,比如Memory Leak等。
缺省安装后,BoundsCheck的设置是每当发现调试状态下运行的程序中的bug就马上中断执行,返回Visual C++窗口报告bug,但是很多BoundsCheck发现的bug都是一些程序隐患,但不影响当前程序运行,所以有些讨厌。可以在BoundsCheck的工具栏中将立即报告错误按钮释放,以后我们就可以不被BoundsCheck打扰,而是每次调试后得到一份BoundsCheck的bug汇总报告!

其他工具

[4] 界面库Xtreme Toolkit

http://www.codejock.com

和Xtreme Toolkit类似的还有BCG Controls,但是我觉得Xtreme Toolkit更好用一些,它们都提供了一整套功能强大、非常漂亮的控件,帮助我们轻松创建出很Cool的程序界面,从而把主要精力放到程序功能上。

[5] IconXP

http://www.aha-soft.com

制作程序的各种图标,如果利用Visual C++或者Delphi等自带的资源编辑器,只能编辑256色的图标,非常麻烦而且基本无法编辑出XP风格的图标来。利用IconXP可以轻松创作出很Cool的图标来,而且IconXP能够从各种文件中提取出图标文件。

写了这么多,累了,明天继续……
[6] OllyDbg

http://home.t-online.de/home/Ollydbg/

这是一个很Cool的静态反汇编工具,并且能够在反汇编代码的基础上对应用程序进行调试。
个人认为OllyDbg比很多crack网站上推荐的WDASM好用,因为OllyDbg加入了很多对反汇编代码的进一步分析功能,并加上相应的注释,非常方便。
比如应用程序在某处调用了Windows API函数,该处后面就会出现注释告诉你这里调用了哪个Windows API函数,更酷的是连给该Windows API传递参数的地方也会加上注释说明。
另外由于很多应用程序都是使用Visual C++编写,而Visual C++生成的汇编代码有一定的格式(如果没有选择某些优化功能的时候),所以OllyDbg甚至会将一些汇编代码对应的C语言代码以注释的方式说明。

OllyDbg本身的调试功能也很强大,多用几次就会得心应手。

总而言之,OllyDbg绝对是在没有源代码的情况下分析应用程序的必备工具。

CodeProject上有两篇文章FreeCell & Hearts, behind the scenes和Minesweeper, Behind the scenes,作者就是以OllyDbg为工具探索到了Windows附带的扫雷游戏、空当接龙游戏的底层数据结构,从而写出了直接读取这些游戏内存的程序,我稍加修改就做了一个自动扫雷的程序,呵呵。

以下程序在http://www.sysinternals.com有提供

[7] DebugView

看过《深入浅出MFC》吗,候捷先生在书的最后提到了一种追踪TRACE(实际上是Windows函数OutputDebugString)的工具。有了该工具,你就可以在应用程序运行时通过它观察追踪应用程序内部的运行情况,只要你在程序中加了足够多的TRACE宏,并且以Debug版本编译。
特别是对于程序逻辑复杂(Debug几次就晕了),或者涉及到图形界面刷新或显示的程序(如果用一台电脑调试,在Visual C++环境和被调试程序之间切换,你很难看到正确的结果),或者非常耗费系统资源的程序(在用Visual C++调试运行,就更费劲了),巧妙的使用这类工具可以高效的解决问题。
说实话,Paul DiLascia等大师固然提供了这些工具,但是这些大师只是为了展示某些技术,所以他们提供的工具都只有基本功能。而DebugView是同类工具中最为优秀的一个,适用范围广,能够定制各种过滤条件,让你只看到关心的TRACE输出信息,而且可以定制高亮显示的内容等等,非常方便。
DebugView是完全免费的!

[8]
Disk Monitor
File Monitor
Register Monitor
Port Monitor


这系列Monitor工具分别对系统中的磁盘、文件、注册表、端口的变化更改进行实时监控并记录下来,对于我们追踪程序对系统进行了那些更改特别有用。

SysInternals上面还有很多工具,都是免费的,有些还提供源代码。

上面是我经常使用的开发辅助工具,有些可能一时没有想到,待以后慢慢在这里补全。
因我主要使用Visual C++进行开发,所以介绍的工具也都主要是和Visual C++相关的,希望有朋友能够将其他主要开发工具的好的配套辅助工具也来个介绍。

另外,如果有朋友需要上面介绍的工具,请跟贴,我试情况上传到FTP上供大家下载。

posted @ 2009-08-19 22:29 chaosuper 阅读(272) | 评论 (0)编辑 收藏

我们很难写出所有可能被实例化的类型都合适的模板。某些情况下,

    通用模板定义对于某个类型可能是完全错误的,所以我们需要能够实现处

    理某些特殊情况,特化的概念变是如此。compare函数和Queue类是这

    个问题的很好例子。因为与C风格字符串一起使用时,他们都不能正确工作

    template <typename T>
    int compare(const T &v1,const T &v2)
    {
      if(v1 < v2) return -1;
      if(v2 < v1) return 1;
      return 0;
    }

        如果用两个const char* 实参调用这个模板定义,函数将比较指针的
    值。也就是比较两个指针在内存中的相对位置,却并没有说明与指针所指
    数组的内容有关的任何事情。
        为了能够将compare函数用于字符串,必须提供一个知道怎样比较C风
    格字符串的特殊定义。这些就被称作是特化的,它对模板的用户而言是透
    明的。


    1. 函数模板的特化
    特化形式:
    - 关键字template后面接一对空的尖括号<>;
    - 再接模板名和一对尖括号<>,尖括号中指定这个特化定义的模板参数:
    - 函数形参表
    - 函数体

    template<>
    int compare<const char*> (const char* const &v1,
         const char* const &v2)
    {
      return strcmp(v1,v2);
    }
        特化的声明必须与 对应的模板相匹配。类型形参固定为const char*。
    因此,函数形参是const char* 的const引用。当调用compare函数的时候,
    传给它两个字符指针,编译器将调用特化版本。而不调用上面的泛型版本。
      const  char *cp1 = "world", *cp2 = "hi";
      int i1, i2;
      compare(cp1, cp2); //调用特化函数模板
      compare(i1, i2);   //调用泛型函数模板

    注意:
    * 函数模板特化时template<>不能省略,如果缺少结果是声明该函数的重载


    * 必须包含函数形参列表。如果可以从形参列表推断模板实参,则不必显示

       定模板实参。
    * 如果程序由多个文件构成,模板特化的声明必须在使用该特化的每个文件
       中出现。


    2.类模板的特化
      当使用C风格字符串时,Queue类具有 compare函数相似的问题。问题就处
    在push函数中,该函数复制给定的值以创建Queue中的新元素。默认情况下


    复制C风格字符串只会复制指针,不会复制字符。而显然复制指针将出现一


    列的严重问题。为了解决复制C风格字符串的问题,需要为const char*定义
    整个类的特化版本:
      template<> class Queue<const char*>{
      public:
        void push(const char*);
        void pop() {real_queue.pop();}
        bool empty() const {return real_queue.front();}
        //返回类型与模板参数类型不同
        std::string front() {return real_queue.front();}
        const std::string &front() const {return real_queue.front();

      private :
        Queue<std::string> real_queue;
    };
    给Queue一个新的数据元素,string对象的Queue。
    在类的外部定义一个成员:
      void Queue<const char*>::push (const char* val)
      {
        return real_queue.push(val);
      }
    这个函数通过调用read_queue的push函数把val指向的数组复制到未命名的
    string 对象中。当需要出队列的时候调用相应real_queue.pop()函数即返
    回了这个string,从而解决了不用复制指针的问题。

   3.特化成员而不特化类
    在上例的实现中,我们可以换一种方法,即不需要特化类,而只需要特化类
    的成员函数push、pop。

    根据函数模板特化的要求:
    template <>
    void Queue<const char*>::push(const char *const &val)
    {
      char * new_item = new char[strlen(val)+1];
      strncpy(new_item, val, strlen(val)+1);
      QueueItem<const char*> *pt =
     new QueueItem<const char*>(new_item);
      if(empty())
        head = tail = pt;   //队列中没有元素
      eles{
        tail->next = pt; //添加新元素到列尾
        tail = pt;
      }
    }

    template<>
    void Queue<const char*>::pop()
    {
      QueueItem<const char*> *p = head;
      delete head->item;  //删除队首元素
      head = head->next;  //指向当前队首元素
      delete p;           //删除零时指针
    }


    4.类模板的部分特化
    如果类模板有一个以上的模板形参,我们很有可能只要特化某些模板形参
    而不是全部形参。这时我们就需要使用类的部分特化。

    //定义模板类
    template <class T1, class T2>
    class some_template{
      // ...
    };

    //定义模板类的部分特化:T2类型固定,部分特化T1类型
    template<class T1>
    class some_template<T1, int>{
      // ...
    };

    //使用类模板的部分特化
    some_template<int, string> foo; //使用模板类
    some_template<string,int> bar;  //使用模板类的部分特化

    通过使用模板特化能解决一些在通常或者通用情况下无法解决的特殊问题。

    在掌握了基本的语法规范和实现方法后便可以加以应用。

 

posted @ 2009-08-11 18:58 chaosuper 阅读(537) | 评论 (0)编辑 收藏

Q:什么是C风格转换?什么是static_cast, dynamic_cast 以及

reinterpret_cast?区别是什么?为什么要注意?

A:转换的含义是通过改变一个变量的类型为别的类型从而改变该变量的表示方式

。为了类型转换一个简单对象为另一个对象你会使用传统的类型转换操作符。比

如,为了转换一个类型为doubole的浮点数的指针到整型:
代码:
int i;
double d;

i = (int) d;
或者:

i = int (d);

对于具有标准定义转换的简单类型而言工作的很好。然而,这样的转换符也能不

分皂白的应用于类(class)和类的指针。ANSI-C++标准定义了四个新的转换符:

'reinterpret_cast', 'static_cast', 'dynamic_cast' 和 'const_cast',目的

在于控制类(class)之间的类型转换。
代码:
reinterpret_cast<new_type>(expression)
dynamic_cast<new_type>(expression)
static_cast<new_type>(expression)
const_cast<new_type>(expression)


1 reinterpret_cast

'reinterpret_cast'转换一个指针为其它类型的指针。它也允许从一个指针转换

为整数类型。反之亦然。(译注:是指针具体的地址值作为整数值?)
这个操作符能够在非相关的类型之间转换。操作结果只是简单的从一个指针到别

的指针的值的二进制拷贝。在类型之间指向的内容不做任何类型的检查和转换。

如果情况是从一个指针到整型的拷贝,内容的解释是系统相关的,所以任何的实

现都不是方便的。一个转换到足够大的整型能够包含它的指针是能够转换回有效

的指针的。

代码:
class A {};
class B {};

A * a = new A;
B * b = reinterpret_cast<B *>(a);
'reinterpret_cast'就像传统的类型转换一样对待所有指针的类型转换。

2 static_cast

'static_cast'允许执行任意的隐式转换和相反转换动作。(即使它是不允许隐式

的)

应用到类的指针上,意思是说它允许子类类型的指针转换为父类类型的指针(这

是一个有效的隐式转换),同时,也能够执行相反动作:转换父类为它的子类。

在这最后例子里,被转换的父类没有被检查是否与目的类型相一致。
代码:
class Base {};
class Derived : public Base {};

Base *a    = new Base;
Derived *b = static_cast<Derived *>(a);
'static_cast'除了操作类型指针,也能用于执行类型定义的显式的转换,以及基

础类型之间的标准转换:

代码:
double d = 3.14159265;
int    i = static_cast<int>(d);

3 dynamic_cast

'dynamic_cast'只用于对象的指针和引用。当用于多态类型时,它允许任意的隐

式类型转换以及相反过程。不过,与static_cast不同,在后一种情况里(注:即

隐式转换的相反过程),dynamic_cast会检查操作是否有效。也就是说,它会检

查转换是否会返回一个被请求的有效的完整对象。
检测在运行时进行。如果被转换的指针不是一个被请求的有效完整的对象指针,

返回值为NULL.
代码:
class Base { virtual dummy() {} };
class Derived : public Base {};

Base* b1 = new Derived;
Base* b2 = new Base;

Derived* d1 = dynamic_cast<Derived *>(b1);          // succeeds
Derived* d2 = dynamic_cast<Derived *>(b2);          // fails: returns

'NULL'

如果一个引用类型执行了类型转换并且这个转换是不可能的,一个bad_cast的异

常类型被抛出:
代码:
class Base { virtual dummy() {} };
class Derived : public Base { };

Base* b1 = new Derived;
Base* b2 = new Base;

Derived d1 = dynamic_cast<Derived &*>(b1);          // succeeds
Derived d2 = dynamic_cast<Derived &*>(b2);          // fails: exception

thrown

4 const_cast

这个转换类型操纵传递对象的const属性,或者是设置或者是移除:
代码:
class C {};

const C *a = new C;

C *b = const_cast<C *>(a);
其它三种操作符是不能修改一个对象的常量性的。
注意:'const_cast'也能改变一个类型的volatile qualifier。

--------------------------------------------------------------------

C++的4种类型转换

    一、C 风格(C-style)强制转型如下:

    (T) expression // cast expression to be of type T
    函数风格(Function-style)强制转型使用这样的语法:
    T(expression) // cast expression to be of type T
    这两种形式之间没有本质上的不同,它纯粹就是一个把括号放在哪的问题。

我把这两种形式称为旧风格(old-style)的强制转型。

   二、 C++的四种强制转型形式:

  C++ 同时提供了四种新的强制转型形式(通常称为新风格的或 C++ 风格的强

制转型):
  const_cast(expression)
  dynamic_cast(expression)
  reinterpret_cast(expression)
  static_cast(expression)

  每一种适用于特定的目的:

  ·dynamic_cast 主要用于执行“安全的向下转型(safe downcasting)”,

也就是说,要确定一个对象是否是一个继承体系中的一个特定类型。它是唯一不

能用旧风格语法执行的强制转型,也是唯一可能有重大运行时代价的强制转型。
   
    ·static_cast 可以被用于强制隐型转换(例如,non-const 对象转型为

const 对象,int 转型为 double,等等),它还可以用于很多这样的转换的反向

转换(例如,void* 指针转型为有类型指针,基类指针转型为派生类指针),但

是它不能将一个 const 对象转型为 non-const 对象(只有 const_cast 能做到

),它最接近于C-style的转换。
   
  ·const_cast 一般用于强制消除对象的常量性。它是唯一能做到这一点的

C++ 风格的强制转型。

  ·reinterpret_cast 是特意用于底层的强制转型,导致实现依赖

(implementation-dependent)(就是说,不可移植)的结果,例如,将一个指

针转型为一个整数。这样的强制转型在底层代码以外应该极为罕见。
  
  旧风格的强制转型依然合法,但是新的形式更可取。首先,在代码中它们更

容易识别(无论是人还是像 grep 这样的工具都是如此),这样就简化了在代码

中寻找类型系统被破坏的地方的过程。第二,更精确地指定每一个强制转型的目

的,使得编译器诊断使用错误成为可能。例如,如果你试图使用一个 const_cast

以外的新风格强制转型来消除常量性,你的代码将无法编译。

== 
==  dynamic_cast .vs. static_cast
==

class B { ... };
class D : public B { ... };

void f(B* pb)
{
   D* pd1 = dynamic_cast<D*>(pb);
   D* pd2 = static_cast<D*>(pb);
}

If pb really points to an object of type D, then pd1 and pd2 will get

the same value. They will also get the same value if pb == 0.

If pb points to an object of type B and not to the complete D class,

then dynamic_cast will know enough to return zero. However, static_cast

relies on the programmer’s assertion that pb points to an object of

type D and simply returns a pointer to that supposed D object.

    即dynamic_cast可用于继承体系中的向下转型,即将基类指针转换为派生类

指针,比static_cast更严格更安全。dynamic_cast在执行效率上比static_cast

要差一些,但static_cast在更宽上范围内可以完成映射,这种不加限制的映射伴随

着不安全性.static_cast覆盖的变换类型除类层次的静态导航以外,还包括无映射

变换,窄化变换(这种变换会导致对象切片,丢失信息),用VOID*的强制变换,隐式类

型变换等...


==
==  static_cast .vs. reinterpret_cast
==

    reinterpret_cast是为了映射到一个完全不同类型的意思,这个关键词在我们

需要把类型映射回原有类型时用到它.我们映射到的类型仅仅是为了故弄玄虚和其

他目的,这是所有映射中最危险的.(这句话是C++编程思想中的原话)

    static_cast 和 reinterpret_cast 操作符修改了操作数类型. 它们不是互

逆的; static_cast 在编译时使用类型信息执行转换, 在转换执行必要的检测(诸

如指针越界计算, 类型检查). 其操作数相对是安全的. 另一方面,

reinterpret_cast 仅仅是重新解释了给出的对象的比特模型而没有进行二进制转

换, 例子如下:

    int n=9; double d=static_cast < double > (n);

    上面的例子中, 我们将一个变量从 int 转换到 double. 这些类型的二进制

表达式是不同的. 要将整数 9 转换到 双精度整数 9, static_cast 需要正确地

为双精度整数 d 补足比特位. 其结果为 9.0. 而reinterpret_cast 的行为却不

同:

    int n=9;
    double d=reinterpret_cast<double & > (n);

    这次, 结果有所不同. 在进行计算以后, d 包含无用值. 这是因为

reinterpret_cast 仅仅是复制 n 的比特位到 d, 没有进行必要的分析.

posted @ 2009-08-07 19:13 chaosuper 阅读(2535) | 评论 (0)编辑 收藏

Linux环境下的软件安装,并不是一件容易的事情;如果通过源代码编译后在安装,当然事情就更为复杂一些;现在安装各种软件的教程都非常普遍;但万变不离其中,对基础知识的扎实掌握,安装各种软件的问题就迎刃而解了。Configure脚本配置工具就是基础之一,它是autoconf的工具的基本应用。
      
    与一些技巧相比,Configure显得基础一些,当然使用和学习起来就显得枯燥乏味一些,当然要成为高手,对基础的熟悉不能超越哦。
      
    为此我转载了一篇关于Configure选项配置的详细介绍。供大家参考

    'configure'脚本有大量的命令行选项.对不同的软件包来说,这些选项可能会有变化,但是许多基本的选项是不会改变的.带上'--help'选项执行'configure'脚本可以看到可用的所有选项.尽管许多选项是很少用到的,但是当你为了特殊的需求而configure一个包时,知道他们的存在是很有益处的.下面对每一个选项进行简略的介绍:

    --cache-file=FILE
      'configure'会在你的系统上测试存在的特性(或者bug!).为了加速随后进行的配置,测试的结果会存储在一个cache file里.当configure一个每个子树里都有'configure'脚本的复杂的源码树时,一个很好的cache file的存在会有很大帮助.

    --help
      输出帮助信息.即使是有经验的用户也偶尔需要使用使用'--help'选项,因为一个复杂的项目会包含附加的选项.例如,GCC包里的'configure'脚本就包含了允许你控制是否生成和在GCC中使用GNU汇编器的选项.

    --no-create
      'configure'中的一个主要函数会制作输出文件.此选项阻止'configure'生成这个文件.你可以认为这是一种演习(dry run),尽管缓存(cache)仍然被改写了.

    --quiet
    --silent
      当'configure'进行他的测试时,会输出简要的信息来告诉用户正在作什么.这样作是因为'configure'可能会比较慢,没有这种输出的话用户将会被扔在一旁疑惑正在发生什么.使用这两个选项中的任何一个都会把你扔到一旁.(译注:这两句话比较有意思,原文是这样的:If there was no such output, the user would be left wondering what is happening. By using this option, you too can be left wondering!)

    --version
      打印用来产生'configure'脚本的Autoconf的版本号.

    --prefix=PEWFIX
      '--prefix'是最常用的选项.制作出的'Makefile'会查看随此选项传递的参数,当一个包在安装时可以彻底的重新安置他的结构独立部分. 举一个例子,当安装一个包,例如说Emacs,下面的命令将会使Emacs Lisp file被安装到"/opt/gnu/share":
    $ ./configure --prefix=/opt/gnu

    --exec-prefix=EPREFIX
      与'--prefix'选项类似,但是他是用来设置结构倚赖的文件的安装位置.编译好的'emacs'二进制文件就是这样一个问件.如果没有设置这个选项的话,默认使用的选项值将被设为和'--prefix'选项值一样.

    --bindir=DIR
      指定二进制文件的安装位置.这里的二进制文件定义为可以被用户直接执行的程序.

    --sbindir=DIR
      指定超级二进制文件的安装位置.这是一些通常只能由超级用户执行的程序.

    --libexecdir=DIR
      指定可执行支持文件的安装位置.与二进制文件相反,这些文件从来不直接由用户执行,但是可以被上面提到的二进制文件所执行.

    --datadir=DIR
      指定通用数据文件的安装位置.

    --sysconfdir=DIR
      指定在单个机器上使用的只读数据的安装位置.

    --sharedstatedir=DIR
      指定可以在多个机器上共享的可写数据的安装位置.

    --localstatedir=DIR
      指定只能单机使用的可写数据的安装位置.

    --libdir=DIR
      指定库文件的安装位置.

    --includedir=DIR
      指定C头文件的安装位置.其他语言如C++的头文件也可以使用此选项.

    --oldincludedir=DIR
      指定为除GCC外编译器安装的C头文件的安装位置.

    --infodir=DIR
      指定Info格式文档的安装位置.Info是被GNU工程所使用的文档格式.

    --mandir=DIR
      指定手册页的安装位置.

    --srcdir=DIR
      这个选项对安装没有作用.他会告诉'configure'源码的位置.一般来说不用指定此选项,因为'configure'脚本一般和源码文件在同一个目录下.

    --program-prefix=PREFIX
      指定将被加到所安装程序的名字上的前缀.例如,使用'--program-prefix=g'来configure一个名为'tar'的程序将会使安装的程序被命名为'gtar'.当和其他的安装选项一起使用时,这个选项只有当他被`Makefile.in'文件使用时才会工作.

    --program-suffix=SUFFIX
      指定将被加到所安装程序的名字上的后缀.

    --program-transform-name=PROGRAM
      这里的PROGRAM是一个sed脚本.当一个程序被安装时,他的名字将经过`sed -e PROGRAM'来产生安装的名字.

    --build=BUILD
      指定软件包安装的系统平台.如果没有指定,默认值将是'--host'选项的值.

    --host=HOST
      指定软件运行的系统平台.如果没有指定,将会运行`config.guess'来检测.

    --target=GARGET
      指定软件面向(target to)的系统平台.这主要在程序语言工具如编译器和汇编器上下文中起作用.如果没有指定,默认将使用'--host'选项的值.

    --disable-FEATURE
      一些软件包可以选择这个选项来提供为大型选项的编译时配置,例如使用Kerberos认证系统或者一个实验性的编译器最优配置.如果默认是提供这些特性,可以使用'--disable-FEATURE'来禁用它,这里'FEATURE'是特性的名字.例如:

   $ ./configure --disable-gui

    -enable-FEATURE[=ARG]
      相反的,一些软件包可能提供了一些默认被禁止的特性,可以使用'--enable-FEATURE'来起用它.这里'FEATURE'是特性的名字.一个特性可能会接受一个可选的参数.例如:
    $ ./configure --enable-buffers=128
    `--enable-FEATURE=no'与上面提到的'--disable-FEATURE'是同义的.

    --with-PACKAGE[=ARG]
      在自由软件社区里,有使用已有软件包和库的优秀传统.当用'configure'来配置一个源码树时,可以提供其他已经安装的软件包的信息.例如,倚赖于Tcl和Tk的BLT器件工具包.要配置BLT,可能需要给'configure'提供一些关于我们把Tcl和Tk装的何处的信息:
    $ ./configure --with-tcl=/usr/local --with-tk=/usr/local
    '--with-PACKAGE=no'与下面将提到的'--without-PACKAGE'是同义的.

    --without-PACKAGE
      有时候你可能不想让你的软件包与系统已有的软件包交互.例如,你可能不想让你的新编译器使用GNU ld.通过使用这个选项可以做到这一点:
    $ ./configure --without-gnu-ld

    --x-includes=DIR
      这个选项是'--with-PACKAGE'选项的一个特例.在Autoconf最初被开发出来时,流行使用'configure'来作为Imake的一个变通方法来制作运行于X的软件.'--x-includes'选项提供了向'configure'脚本指明包含X11头文件的目录的方法.

    --x-libraries=DIR
      类似的,'--x-libraries'选项提供了向'configure'脚本指明包含X11库的目录的方法.

      在源码树中运行'configure'是不必要的同时也是不好的.一个由'configure'产生的良好的'Makefile'可以构筑源码属于另一棵树的软件包.在一个独立于源码的树中构筑派生的文件的好处是很明显的:派生的文件,如目标文件,会凌乱的散布于源码树.这也使在另一个不同的系统或用不同的配置选项构筑同样的目标文件非常困难.建议使用三棵树:一棵源码树(source tree),一棵构筑树(build tree),一棵安装树(install tree).这里有一个很接近的例子,是使用这种方法来构筑GNU malloc包:

    $ gtar zxf mmalloc-1.0.tar.gz

    $ mkdir build && cd build

    $ ../mmalloc-1.0/configure

    creating cache ./config.cache

    checking for gcc... gcc
    checking whether the C compiler (gcc ) works... yes

    checking whether the C compiler (gcc ) is a cross-compiler... no

    checking whether we are using GNU C... yes

    checking whether gcc accepts -g... yes

    checking for a BSD compatible install... /usr/bin/install -c

    checking host system type... i586-pc-linux-gnu

    checking build system type... i586-pc-linux-gnu

    checking for ar... ar
    checking for ranlib... ranlib

    checking how to run the C preprocessor... gcc -E

    checking for unistd.h... yes

    checking for getpagesize... yes

    checking for working mmap... yes

    checking for limits.h... yes

    checking for stddef.h... yes

    updating cache ../config.cache
    creating ./config.status

    这样这棵构筑树就被配置了,下面可以继续构筑和安装这个包到默认的位置'/usr/local':

    $ make all && make install

一个软件包通过编译源代码安装后,如何完全的卸载??

    如果原先的source还在的话,很多source的Makefile都有写uninstall规则,直接在Souce里make uninstall就可行,不过碰到无良作者没写的,那一句一句看Makefile里install部分他都干了些什么,然后挨个删除。
    如果source没了.....那就一边郁闷吧
    到目前为止, 我装的都可以make uninstall.......
    (因为总是不小心装错地方, 结果就make uninstall&&make clean,然后重新configure......)
    linux下软件的基本安装和卸载

    Linux软件的安装和卸载一直是困扰许多新用户的难题。在Windows中,我们可以使用软件自带的安装卸载程序或在控制面板中的“添加/删除程序”来实现。与其相类似,在Linux下有一个功能强大的软件安装卸载工具,名为RPM。它可以用来建立、安装、查询、更新、卸载软件。该工具是在命令行下使用的。在Shell的提示符后输入rpm,就可获得该命令的帮助信息。

    软件的安装

    Linux下软件的安装主要有两种不同的形式。第一种安装文件名为xxx.tar.gz;另一种安装文件名为xxx.i386.rpm。以第一种方式发行的软件多为以源码形式发送的;第二种方式则是直接以二进制形式发送的。

    对于第一种,安装方法如下:

    1 .首先,将安装文件拷贝至你的目录中。例如,如果你是以root身份登录上的,就将软件拷贝至/root中。

    #cp xxx.tar.gz /root

    2 .由于该文件是被压缩并打包的,应对其解压缩。命令为:

    #tar xvzf filename.tar.gz 如果是filename.tar.bz2格式的,应该是tar jxvf filename.tar.bz2来解压

    3. 执行该命令后,安装文件按路径,解压缩在当前目录下。用ls命令可以看到解压缩后的文件。通常在解压缩后产生的文件中,有“Install”的文件。该文件为纯文本文件,详细讲述了该软件包的安装方法。

    4.执行解压缩后产生的一个名为configure的可执行脚本程序。它是用于检查系统是否有编译时所需的库,以及库的版本是否满足编译的需要等安装所需要的系统信息。为随后的编译工作做准备。命令为: #./configure

    如果您想把软件安装到指定目录,应该用#./configure --prefix=/您自己指定的目录,比如我想把一个mlterm安装到/opt/mlterm目录中,应该如下输入

    #./configure --prefix=/opt/mlterm

    5.检查通过后,将生成用于编译的MakeFile文件。此时,可以开始进行编译了。编译的过程视软件的规模和计算机性能的不同,所耗费的时间也不同。命令为: #make。

    6.成功编译后,键入如下的命令开始安装:

    #make install

    7.安装完毕,应清除编译过程中产生的临时文件和配置过程中产生的文件。键入如下命令:

    #make clean

    #make distclean

    至此,软件的安装结束。

    对于第二种,其安装方法要简单得多。

    同第一种方式一样,将安装文件拷贝至你的目录中。然后使用rpm来安装该文件。命令如下:

    #rpm -i filename.i386.rpm

    rpm将自动将安装文件解包,并将软件安装到缺省的目录下。并将软件的安装信息注册到rpm的数据库中。参数i的作用是使rpm进入安装模式。

    软件的卸载

    1.软件的卸载主要是使用rpm来进行的。卸载软件首先要知道软件包在系统中注册的名称。键入命令:

    #rpm -q -a

    即可查询到当前系统中安装的所有的软件包。

    2. 确定了要卸载的软件的名称,就可以开始实际卸载该软件了。键入命令:

    #rpm -e [package name]

    即可卸载软件。参数e的作用是使rpm进入卸载模式。对名为[package name]的软件包进行卸载。由于系统中各个软件包之间相互有依赖关系。如果因存在依赖关系而不能卸载,rpm将给予提示并停止卸载。你可以使用如下的命令来忽略依赖关系,直接开始卸载:

    #rpm -e [package name] -nodeps

    忽略依赖关系的卸载可能会导致系统中其它的一些软件无法使用

    如果想知道rpm包安装到哪里了呢?

    应该用 #rpm -ql [package name]

    3.如何卸载用源码包安装的软件?

    最好是看README和INSTALL ;一般的情况下都有说,但大多软件没有提供源码包的卸载方法;我们可以找到软件的安装点删除。主要看你把它安装在哪了。

    比如:

    如果安装软件时,指定个目录。这个问题也不会难;

    比如用源码包安装gaim 的

    #./configure --prefix=/opt/gaim

    #make

    #make install

    如果安装mlterm

    #./configure --prefix=/opt/mlterm

    #make

    #make install

    把源码包安装的软件,都指定安装在 /opt目录中,这样不就知道了??

    如果删除,就删除相应的软件目录;

    有些软件要在解压安装目录中执行 make uninstall ,这样就卸载掉了

posted @ 2009-08-06 15:26 chaosuper 阅读(111) | 评论 (0)编辑 收藏

   用VC来写程序,有时总是出这样那样的问题,没办法只能自己上网查资料来解决,在这里把自己常见的问题和一些技巧贴出来分享给大家,希望对大家有用,也省去大家再去搜索的烦恼……

    1.如何在Release状态下进行调试

    Project->Setting=>ProjectSetting对话框,选择Release状态。C/C++标签中的Category选General,Optimizations选Disable(Debug),Debut info选Program Database.在Link标签中选中Generate debug info复选框。

    注:只是一个介乎Debug和Release的中间状态,所有的ASSERT、VERIFY都不起作用,函数调用方式已经是真正的调用,而不查表,但是这种状态下QuickWatch、调用队列跟踪功能仍然有效,和Debug版一样。

    2. Release和Debug有什么不同

    Release版称为发行版,Debug版称为调试版。

    Debug中可以单步执行、跟踪等功能,但生成的可执行文件比较大,代码运行速度较慢。Release版运行速度较快,可执行文件较小,但在其编译条件下无法执行调试功能。

    Release的exe文件链接的是标准的MFC DLL(Use MFC in a shared or static dll)。这些DLL在安装Windows的时候,已经配置,所以这些程序能够在没有安装Visual C++ 6.0的机器上运行。而Debug版本的exe链接了调试版本的MFC DLL文件,在没有安装Visual C++6.0的机器上不能运行,因为缺相应的DLL,除非选择use static dll when link.

    3. ASSERT和VERIFY有什么区别

    ASSERT里面的内容在Release版本中不编译,VERIFY里面的内容仍然翻译,但不再判断真假。所以后者更安全一点。例如ASSERT(file.Open(strFileName))。一旦到了Release版本中,这一行就忽略了,file根本就不Open()了,而且没有任何出错的信息。如果用VERIFY()就不会有这个问题。

    4.Workspace和Project之间是什么样的关系

    每个Workspace可以包括几个project,但只有一个处于Active状态,各个project之间可以有依赖关系,在project的Setting……中可以设定,比如那个Active状态的project可以依赖于其他的提供其函数调用的静态库。

    5. 如何在非MFC程序中使用ClassWizard

    在工程目录下新建一个空的。RC文件,然后加入到工程中就可以了。

    6.如何设置断点

    按F9在当前光标处增加一个断点和取消一个断点。另外,在编辑状态下,按Ctrl+B组合键,弹出断点设置对话框。然后单击「Condition…」按钮弹出设置断点条件的对话框进行设置。

    7.在编辑状态下发现成员变量或函数不能显示提示是如何打开显示功能

    这似乎是目前这个Visual C++ 6.0版本的一个bug,可按如下步骤使其正常,如再出现,可如法炮制:

    (1)关闭Project

    (2)删除“工程名。ncb”文件

    (3)重新打开工程

    8.如何将一个通过ClassWizard生成的类彻底删除

    首先在工作区的FileView中选中该类的。h和。cpp文件,按delete删除,然后在文件管理器中将这两个文件删除,再运行ClassWizard,这时出现是否移走该类的提示,选择remove就可以了。

    9. 如何将在workspace中消失的类找出来

    打开该类对应的头文件,然后将其类名随便改一下,这个时候工作区就会出现新的类,再将这个类改回原来的名字就可以了。

    10. 如何清除所有的断点

    菜单「Edit」->「Breakpoints…」,打开“Breakpoints”对话框,单击「Remove All」按钮即可。快捷键是“Ctrl + Shift + F8”。

    11. 如何再ClassWizard中选择未列出的信息

    打开“ClassWizard”对话框,然后切换到“Class Info”页面。改变“Message filter”,如选择“Window”,“Message”页面就会出现Window的信息。

    12. 如何检测程序中的括号是否匹配

    把光标移动到需要检测的括号前面,按快捷键“Ctrl + ]”。如果括号匹配正确,光标就跳到匹配的括号处,否则光标不移动,并且机箱喇叭还会发出一声警告。

    13. 如何查看一个宏(或变量、函数)的定义

    把光标移动到要查看的一个宏上,就比如说最常见的DECLARE_MAP_MESSAGE上按一下F12(或右键菜单中的相关菜单),如果没有建立浏览文件,就会出现提示对话框,按「确定」按钮,然后就会跳到该宏(或变量、函数)定义的地方。

    14. 如何添加Lib文件到当前工程

    单击菜单「Project」->「Settings…」弹出“Project Setting”对话框,切换到“Link”标签页,在“Object/library modules”处输入Lib文件名称,不同的Lib之间用空格格开。

    15. 如何快速删除项目下的Debug文件夹中临时文件

    在工作区的FileView视图中选中对应的项目,单击右键弹出菜单,选择「Clean(selection only)」菜单即可。

    16. 如何快速生成一个现有工程除了工程名外完全相同的新工程

    在新建工程的“New”对话框中选择“Custom Appwizard”项,输入新工程的名字,单击「OK」按钮。出现“Custom AppWizard”项,输入新工程的名字,单击「OK」按钮。出现“Custom AppWizard-Step 1 of 2”对话框,选择“An existing Project”项,单击「Next」按钮。出现“Custom AppWizard-Step 2 of 2”对话框,选择现有工程的工程文件名,最后单击「Finish」按钮。编译后就生成一个与现有工程相同但可以重新取名的工程AppWizard.现在就可以项用MFC AppWizard一样用这个定制的向导。如果不想用了,可以在Visual C++ 6.0安装目录下Common\MSDev98\Template目录中删除该Wizard对应的。awx和。pdb文件。

    17. 如何解决Visual C++ 6.0不正确连接的问题

    情景:明明改动了一个文件,却要把整个项目全部重新编译链接一次。刚刚链接好,一运行,又提示重新编译链接一次。这是因为出现了未来文件(修改时间和创建时间比系统时间晚)的缘故。可以这样处理:找到工程文件夹下的debug目录,将创建和修改时间都比系统时间的文件全部删除,然后再从新“Rebuild All”一次。

    18. 引起LNK2001的常见错误都有哪些

    遇到的LNK2001错误主要为:unresolved external symbol “symbol”,如果链接程序不能在所有的库和目标文件内找到所引用的函数、变量或标签,将产生此错误信息。

    一般来说,发生错误的原因有两个:一是所引用的函数、变量不存在,拼写不正确或者使用错误;其次可能使用了不同版本的链接库。以下是可能产生LNK2001错误的原因:

    <1>由于编码错误导致的LNK2001错误

    (1)不相匹配的程序代码或模块定义(。DEF)文件导致LNK2001.例如,如果在C++源文件了内声明了一变量“var1”,却试图在另一个文件内以变量“var1”访问改变量。

    (2)如果使用的内联函数是在。cpp文件内定义的,而不是在头文件内定义将导致LNK2001错误。

    (3)调用函数时如果所用的参数类型和头函数声明时的类型不符将会产生LNK2001错误。

    (4)试图从基类的构造函数或析构函数中调用虚拟函数时将会导致LNK2001错误。

    (5)要注意函数和变量的可公用性,只有全局变量、函数是可公用的。静态函数和静态变量具有相同的使用范围限制。当试图从文件外部方位任何没有在该文件内声明的静态变量时将导致编译错误或LNK2001错误。

    <2>由于编译和联机的设置而造成的LNK2001错误

    (1)如果编译时使用的是/NOD(/NODERAULTLIB)选项,程序所需要的运行库和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错误。

    19. 如何调试一个没有源码的exe文件调用的dll

    在Visual C++ 6.0中,进入“Project Setting”对话框然后选择Debug标签页。通常Visual Studio默认“executable for debug session”为可执行文件名,但可以将他改成任何你想要的程序。甚至可以指定不同的工作目录以及传递参数到你的程序。这个技术常用来调试Dlls、名字空间扩展、COM对象和其他从某些EXE以及从第三方的EXE中调用的plug-in程序。

    20.   Visual C++ 6.0工程中的项目文件都表示什么

    ·opt:工程关于开发环境的参数文件。如工具条位置等信息。

    ·aps(AppStudio File)资源辅助文件,二进制格式,一般不用去管它。

    ·clw:ClassWizard信息文件,实际上是INI文件格式,有兴趣可以研究一下。有时候ClassWizard出了问题,手工修改CLW文件可以解决。如果此文件不存在的话,每次用ClassWizard的时候回提示是否重建。

    ·dsp(DevelopStudio Project):项目文件,文本格式,不过不熟悉的不要手工修改。

    ·dsw(DevelopStudio Workspace):是工作区文件,其他特点和。dsp差不多。

    ·plg:是编译信息文件,编译时的error和warning信息文件(实际上是一个html文件),一般用处不大。在单击菜单「Tool」->「Option」弹出的对话框里面有个选项可以控制这个文件的生成。

    ·hpj(Help Project):是生成帮助文件的工程,用microsoft Help Compiler可以处理。

    ·mdp(Microsoft DevStudio Project):是旧版本的项目文件,如果要打开此文件的话,会提示你是否转换成新的。dsp格式。

    ·bsc:是用于浏览项目信息的,如果用Source Brower的话就必须有这个文件。如果不用这个功能的话,可以在Project Options里面去掉Generate Browse Info File,这样可以加快编译速度。

    ·map是执行文件的映象信息记录文件,除非对系统底层,这个文件一般用不着。

    ·pch(Pre-Compiled File):是与编译文件,可以加快编译速度,但是文件非常大。

    ·pdb(Program Database):记录了程序有关的一些数据和调试信息,在调试的时候可能有用。

    ·exp:只有在编译DLL的时候才会生成,记录了DLL文件的一些信息,一般也没有用。

    ·ncb:无编译浏览文件(no compile browser)。当自动完成功能出问题时可以删除此文件。编译工程后会自动生成。

posted @ 2009-08-06 15:24 chaosuper 阅读(131) | 评论 (0)编辑 收藏

    1. 学会写简单的makefile

    2. 编一应用程序,可以用makefile跑起来

    3. 学会写驱动的makefile

    4. 写一简单char驱动,makefile编译通过,可以insmod, lsmod, rmmod. 在驱动的init函数里打印hello world, insmod后应该能够通过dmesg看到输出。

    5. 写一完整驱动, 加上read, write, ioctl, polling等各种函数的驱动实现。 在ioctl里完成从用户空间向内核空间传递结构体的实现。

    6. 写一block驱动, 加上read,write,ioctl,poll等各种函数实现。

    7. 简单学习下内存管理, 这个是最难的,明白各种memory alloc的函数实现细节。这是Linux开发的基本功。

    8. 学习锁机制的应用,这个不是最难的但是最容易犯错的,涉及到很多同步和并发的问题。

    9. 看内核中实际应用的驱动代码。 你会发现最基本的你已经知道了, 大的框架都是一样的, 无非是read, write, ioctl等函数的实现, 但里面包含了很多很多细小的实现细节是之前不知道的。 这时候就要考虑到很多别的问题而不仅仅是基本功能的实现。

    推荐您看2.6.20中integrated的一个驱动 kvm, 记得是在driver/lguest下,很好玩的, 就是Linux下的虚拟机驱动, 代码不长,但功能强大。有能力的可以自己写一操作系统按照要求做成磁盘镜像加载到虚拟机中, 然后客户机可以有自己的4G虚拟地址空间。

    10. 看完驱动欢迎您进入Linux kernel学习中来。

    最简单的方法,跟着ldd(Linux devive driver)做一遍。

posted @ 2009-08-06 06:16 chaosuper 阅读(171) | 评论 (0)编辑 收藏

Ubuntu 在安装时,如同大部分 Linux 发行版一样,都会同时安装 GNU 版本的 Java。这个 Java 的实用程度太低,尤其对于开发人员来说,是没有太多用处的。在 Ubuntu 下,安装 SUN Java 是一件很容易的事情。第一步:
    sudo apt-get install sun-java5-jdk
安装完毕之后,选择默认 java:
    sudo update-alternatives --config java
然后配置环境变量:
    sudo vim /etc/environment
在其中添加如下两行:
    CLASSPATH=/usr/lib/jvm/java-1.5.0-sun/lib
    JAVA_HOME=/usr/lib/jvm/java-1.5.0-sun
保存退出。

之后安装配置 Eclipse。安装很简单:
    sudo apt-get install eclipse
然已经这时新安装的 java 已经成为系统默认的 jvm,但是 Eclipse 并不会用 update-alternative 设置的 jvm 来启动自身,而使用的是以前的 GNU Java。GNU Java 是 1.4.2 的实现,而且在性能上远不如 SUN 的实现。为了让 Eclipse 利用 SUN Java 启动,我们还需要继续配置。首先将 SUN Java 完完全全的设置为系统的默认 JDK:
    sudo update-java-alternatives -s java-1.5.0-sun
然后编辑 JVM 配置文件:
    sudo vim /etc/jvm
将文件中的
    /usr/lib/jvm/java-1.5.0-sun
这一行移动到配置块的顶部。由于 Eclipse 会忽略 Ubuntu 的通用 Java 设置(貌似一个 bug),我们需要继续编辑 Eclipse 的 java_home 文件:
    sudo vim /etc/eclipse/java_home
如同上面一样,将
    /usr/lib/jvm/java-1.5.0-sun
这一行移动到文件的顶部。

所有的安装配置完成之后,Ubuntu 的 Java 开发平台就基本完备了。
posted @ 2009-08-06 05:29 chaosuper 阅读(68) | 评论 (0)编辑 收藏

    C++虚函数探索笔记(3)——延伸思考:虚函数应用的一些其他情形

    关注问题:

    虚函数的作用

    虚函数的实现原理

    虚函数表在对象布局里的位置

    虚函数的类的sizeof

    纯虚函数的作用

    多级继承时的虚函数表内容

    虚函数如何执行父类代码

    多继承时的虚函数表定位,以及对象布局

    虚析构函数的作用

    虚函数在QT中的应用

    虚函数与inline修饰符,static修饰符

    虚析构函数

    大家都知道,在C++里需要自己严格管理好资源的分配和回收。通常情况下,

在一个对象被析构的时候,是要由其释放其申请到的各种资源的。最常见的,当

然就是内存资源啦。

    当只有一个类的时候,我们可以不用考虑太多,只要在析构函数里检查并释

放所有申请到的资源即可。但是在这个类继承了一个抽象接口基类时,就有点点

不一样了。让我们看看类的析构过程:

    在大多数的类的使用时,通常都是直接删除该类的实例对象,然后该类的析

构函数就会被调用,从而使得这个类在析构函数里执行的资源释放代码被执行到

    如果这个类继承了其他类,那么编译器还会在这个类的析构函数里自动添加

对父类的析构函数的调用,从而将父类里申请的资源也进行释放。如果偶多个父

类,也会依次调用各个析构函数。

    倘若继承的是一个抽象接口类,并且在程序运行期,可能通过一个基类指针

将此对象释放掉,那么致命而又隐藏的内存泄露BUG就出现啦……因为试图删除的

是基对象,删除时调用的是基类的析构函数,而基类的析构函数当然是不会去调

用子类的析构函数的罗!

    让我们看看下面的代码,使用vs2008编译并运行的时候,将会在程序运行结

束时报告内存泄漏情况(如果要在linux下编译测试,需要去掉第一行的include

,以及return前的_CrtDumpMemoryLeaks()函数,然后使用linux下检查内存泄

露的工具进行测试)。
 //Source filename: Win32Con.cpp
#include
class parent
{
public:
 parent() { }
 /*virtual */ ~parent() { }
};

class child:public parent
{
public:
 child()
 {
  p=new char[1000];
 }
 ~child()
 {
  delete[] p;
 }
 char *p;
};

void free_child(parent *pp)
{
 delete pp;
}

int main()
{
 child *obj=new child();
 free_child(obj);
 _CrtDumpMemoryLeaks();
 return 0;
}

 


    在这段代码里我们创建的是一个child类型的对象,然后使用free_child

(parent*)函数来试图释放这个对象,这个时候,只会调用到parent::

~parent()这个析构函数,而不会调用到child::~child()!

    如何解决这个问题呢?

    很简单的,只要在parent::~parent()前增加 virtual关键字,将其变成

一个虚函数。这样,无论是以这个对象的父类指针进行删除的时候,就会从虚函

数表里定位到子类child的析构函数,这样就能够从子类开始一级一级的向上调用

析构函数,从而正确的将这个对象在各个继承层次上申请的所有资源都释放掉。

    正因为这个原因,在很多C++编程原则的文章或者书里都会提到这样的原则:

    如果一个类要被设计为可被继承的基类,那么其析构函数应该被声明为虚函

数。

   虚函数在QT中的应用

    在QT里虚函数的应用非常的广泛,事实上,在大多数的C++类库里都不可避免

的要使用到虚函数。这里简单的列举QT里使用虚函数的情况:

    QT的事件机制

    是使用了虚函数的,你因此才可以自定义事件处理函数。比如最核心的

QObject类的定义里(在qobject.h里),我们可以看到如下的虚函数定义:

    virtual bool event(QEvent *);

    virtual bool eventFilter(QObject *, QEvent *);

    然后,在QWidget类继承QObject类后重新实现了上面的两个虚函数,完成很

多窗口控件类的缺省事件处理。

    当你要编写自定义的QT控件的时候,对event虚函数的重新实现就更是重要啦

    QT的信号和槽

    QT的槽函数可以被声明为虚函数,所以虽然QT在实现信号和槽机制的时候可

能出于效率或者运行代价的原因未采用虚函数机制,但是我们依然可以在必要的

时候使用虚函数来完成一些特定功能。比如为一些自定义控件类抽象出来一个抽

象接口基类,在做信号和槽的连接的时候是对基类指针进行操作,而在基类里的

槽定义为虚函数,那么虚函数在此依然可以实现信号与槽的多态。

    然而虚函数在调用的时候,一定要经历查表的步骤,是存在一定的运行开销

的,对于一些非常频繁的槽调用还是应该考虑到使用虚函数产生的代价的。

    其他

    在虚函数上,static和inline这两个关键词与virtual显得很不友好。

    从语义上即可看出,static和virtual完全就是冲突的,所以如果你试图为一

个虚函数增加一个static限定词,那么你的C++编译器就会很负责任的报告一个严

重错误给你。

    而inline的含义和虚函数其实也是非常冲突的,但是inline在语法上只是给

编译器一个建议,而不是强制的语义限定,所以C++编译器应该会忽略掉inline关

键词,继续正常的编译。

 

posted @ 2009-08-05 17:47 chaosuper 阅读(296) | 评论 (0)编辑 收藏

    多继承与虚函数重复

    既然说到了多继承,那么还有一个问题可能会需要解决,那就是如果两个父类里都有相同的虚函数定义,在子对象的布局里会是怎么样个情况?是否依然可以将这个虚函数指向到正确的实现代码上呢?

    修改前面一个源代码,在parent2的接口里增加下面的虚函数定义:

    virtual int fun1(){cout<<"parent2::fun1()"<<endl;return 0;};

    上面的fun1的定义与parent1类里的完全重复相同(类型,参数列表),增加上面的代码后立即开始编译,程序正常编译通过。运行之,得到下面的结果:

    child1::fun1()

    child1::fun2()

    这个程序居然正确的完成了执行,编译器在其中做了些怎样的工作,是怎么样避免掉队fun1函数的冲突问题呢?

    让我们来看看这个时候的child1的对象布局:
 class child1    size(8):
        +---
        | +--- (base class parent1)
 0      | | {vfptr}
        | +---
        | +--- (base class parent2)
 4      | | {vfptr}
        | +---
        +---

child1::$vftable@parent1@:
        | &child1_meta
        |  0
 0      | &child1::fun1

child1::$vftable@parent2@:
        | -4
 0      | &child1::fun2
 1      | &thunk: this-=4; goto child1::fun1

child1::fun1 this adjustor: 0
child1::fun2 this adjustor: 4

 


    恩~~~还是两个vfptr在child1的对象布局里(不一样就怪啦,呵呵),但是第二个vfptr所指的虚函数表的内容有所变化哦!

    注意看红色字体部分,虚函数表里并没有直接填写child::fun1的代码,而是多了一个 &thunk: this-=4;然后才goto child1::fun1!注意到一个关键名词thunk了吧?没错,vc在这里使用了名为thunk的技术,避免了虚函数fun1在两个基类里重复出现导致的冲突问题!(除了thunk,还有其他方法可以解决此类问题的)。

    现在,我们知道为什么相同的虚函数不会在子类里出现冲突的情况了。

    但是,倘若我们在基类里就是由两个冲突的普通函数,而不是虚函数,是个怎样的情况呢?

    多继承产生的冲突与虚继承,虚基类

    我们在parent1和parent2里添加一个相同的函数void fun3(),然后再进行编译,通过了!查看类对象布局,跟上面的完全一致。但是在main函数里调用chobj.fun3()的时候,编译器却不再能正确编译了,并且会提示“error C2385: 对”fun3“的访问不明确”的错误信息,没错,编译器不知道你要访问哪个fun3了。

    如何解决这样的多继承带来的问题呢,其实有一个简单的做法。就是在方法前限定引用的具体是哪个类的函数,比如:chobj.parent1::fun3();  ,这样的写法就写明了是要调用chobj的父类parent1里的fun3()函数!

    我们再看看另外一种情况,从parent1和parent2里抹去刚才添加的fun3函数,将之放到一个共同的基类里:
     class commonbase

    {

    public:

    void fun3(){cout<<"commonbase::fun3()"<<endl;}

    };
 


    而parent1和parent2都修改为从此类继承。可以看到,在这个情况下,依然需要使用chobj.parent1::fun3();  的方式才可以正确调用到fun3,难道,在这种情况下,就不能自然的使用chobj.fun3()这样的方式了吗?

    虚继承可以解决这个问题——我们在parent1和parent2继承common类的地方添加上一个关键词virtual,如下:
     class parent1:virtual public commonbase

    {

    public:

    virtual int fun1(){cout<<"parent1::fun1()"<<endl;return 0;};

    };
 


    给parent2也同样的处理,然后再次编译,这次chobj.fun3()可以编译通过了!!!

    编译器这次又在私下里做了哪些工作了呢????
 class child1    size(16):
        +---
        | +--- (base class parent1)
 0      | | {vfptr}
 4      | | {vbptr}
        | +---
        | +--- (base class parent2)
 8      | | {vfptr}
12      | | {vbptr}
        | +---
        +---
        +--- (virtual base commonbase)
        +---

child1::$vftable@parent1@:
        | &child1_meta
        |  0
 0      | &child1::fun1

child1::$vftable@parent2@:
        | -8
 0      | &child1::fun2
 1      | &thunk: this-=8; goto child1::fun1

child1::$vbtable@parent1@:
 0      | -4
 1      | 12 (child1d(parent1+4)commonbase)

child1::$vbtable@parent2@:
 0      | -4
 1      | 4 (child1d(parent2+4)commonbase)

child1::fun1 this adjustor: 0
child1::fun2 this adjustor: 8

vbi:       class  offset o.vbptr  o.vbte fVtorDisp
      commonbase      16       4       4 0

 


    这次变化可大了去了!!!

    首先,可以看到两个类parent1和parent2的对象布局里,都多了一个vbptr的指针。而在child1的对象布局里,还有一个virtual base commonbase的虚拟基类。再看看两个vbptr的内容:

    12 (child1d(parent1+4)commonbase) 这个很好理解,从parent1的vbptr开始,偏移12个字节,指向的是virtual base commonbase!

    再看看4 (child1d(parent2+4)commonbase) ,从parent2的vbptr开始,便宜4个字节,也指向了virtual base commonbase!

    这下明白了。虚基类在child1里只有一个共同的对象布局了,所以就可以直接用chobj.fun3()啦,当然,在commonbase里的其他成员变量此时也可以同样的方式访问了!

    虽然解决方案有了,但是在一个系统的设计里,如果有一个基类出现多继承冲突的情况,大部分情况下都说明这样的设计是有问题的,应该尽量避免这样的设计,并且尽量用纯虚函数,来提取一些抽象的接口类,把共同的方法接口都抽取出来,通常就能避免多继承的问题。

 

posted @ 2009-08-05 17:46 chaosuper 阅读(139) | 评论 (0)编辑 收藏

  C++虚函数探索笔记(2)——虚函数与多继承

    关注问题:

    虚函数的作用

    虚函数的实现原理

    虚函数表在对象布局里的位置

    虚函数的类的sizeof

    纯虚函数的作用

    多级继承时的虚函数表内容

    虚函数如何执行父类代码

    多继承时的虚函数表定位,以及对象布局

    虚析构函数的作用

    虚函数在QT的信号与槽中的应用

    虚函数与inline修饰符,static修饰符

    前面我们尝试了一个简单的例子,接下来尝试一个多级继承的例子,以及一个多继承的例子。主要涉及到以下问题:多级继承时虚函数表的内容是如何填写的,如何在多级继承的情况下调用某一级父类里的虚函数,以及在多继承(多个父类)的情况下的对象布局。

    多级继承

    在这里,多级继承指的是有3层或者多层继承关系的情形。让我们看看下面的代码:
 //Source filename: Win32Con.cpp

 #include <iostream>
 using namespace std;
class parent1
 {
public:
    virtual int fun1(){cout<<"parent1::fun1()"<<endl;return 0;};
    virtual int fun2()=0;
};
class child1:public parent1
 {
public:

    virtual int fun1()
    {
        cout<<"child1::fun1()"<<endl;
        parent1::fun1();
        return 0;
    }
    virtual int fun2()
    {
        cout<<"child1::fun2()"<<endl;
        return 0;
    }
};

class grandson:public child1
 {
public:
    virtual int fun2()
    {
        cout<<"grandson::fun2()"<<endl;
        //parent1::fun2();
        parent1::fun1();
        child1::fun2();
        return 0;
    }
};

void test_func1(parent1 *pp)
{
    pp->fun1();
    pp->fun2();
}

int main(int argc, char* argv[])
{
    grandson sunzi;
    test_func1(&sunzi);
    return 0;

 


    这段代码展示了三个class,分别是parent1,child1,grandson.

    类parent1定义了两个虚函数,其中fun2是一个纯虚函数,这个类是一个不可实例化的抽象基类。

    类child1继承了parent1,并且对两个虚函数fun1和fun2都编写了实现的代码,这个类可以被实例化。

    类grandson继承了child1,但是只对虚函数fun2编写了实现的代码。

    此外,我们还改写了test_func1函数,它的参数为parent1类型的指针,我们可以将parent1的子孙类作为这个函数的参数传入。在这个函数里,我们将依次调用parent1类的两个虚函数。

    可以先通过阅读代码预测一下程序的输出内容。

    程序的输出内容将是:

    child1::fun1()

    parent1::fun1()

    grandson::fun2()

    parent1::fun1()

    child1::fun2()

    先看第一行输出child1::fun1(),为什么会输出它呢?我们定义的具体对象sunzi是grandson类型的,test_func1的参数类型是parent1类型。在调用这个虚函数的时候,是完成了一次怎样的调用过程呢?

    让我们再次使用cl命令输出这几个类的对象布局:
 class parent1   size(4):
        +---
 0      | {vfptr}
        +---

parent1::$vftable@:
        | &parent1_meta
        |  0
 0      | &parent1::fun1
 1      | &parent1::fun2

parent1::fun1 this adjustor: 0
parent1::fun2 this adjustor: 0

class child1    size(4):
        +---
        | +--- (base class parent1)
 0      | | {vfptr}
        | +---
        +---

child1::$vftable@:
        | &child1_meta
        |  0
 0      | &child1::fun1
 1      | &child1::fun2

child1::fun1 this adjustor: 0
child1::fun2 this adjustor: 0

class grandson  size(4): //grandson的对象布局
        +---
        | +--- (base class child1)
        | | +--- (base class parent1)
 0      | | | {vfptr}
        | | +---
        | +---
        +---

grandson::$vftable@:  //grandson虚函数表的内容
        | &grandson_meta
        |  0
 0      | &child1::fun1
 1      | &grandson::fun2

grandson::fun2 this adjustor: 0

 


    因为我们实例化的是一个grandson对象,让我们看看它的对象布局。正如前面的例子一样,里面只有一个vfptr指针,但是不一样的却是这个指针所指的虚函数表的内容:

    第一个虚函数,填写的是child1类的fun1的地址;第二个虚函数填写的才是grandson类的fun2的地址。

    很显然我们可以得出这样一个结论:在一个子对象的虚函数表里,每一个虚函数的实际运行的函数地址,将填写为在继承体系里最后实现该虚函数的函数地址。

    所以当我们在test_func1里调用了传入的parent1指针的fun1函数的时候,我们实际执行的是填写在虚函数表里的child1::fun1(),而调用fun2函数的时候,是从虚函数表里得到了grandson::fun2函数的地址并调用之。在“程序输出结果”表里的第一行和第三行结果证实了上述结论。

    再看一下程序代码部分的child1::fun1()的实现代码,在第18行,我们有parent1::fun1();这样的语句,这行代码输出了运行结果里的第二行,而在grandson::fun2()的实现代码第35行的parent1::fun1();以及第36行的child1::fun2();则输出了运行结果里的第四行和第五行的内容。这三行代码展示了如何调用父类以及更高的祖先类里的虚函数。——事实上,这与调用父类的普通函数没有任何区别。

    在程序代码的第34行,有一行被注释了的内容//parent1::fun2();,之所以会注释掉,是因为这样的代码是无法通过编译的,因为在parent1类里,fun2是一个“纯虚函数”也就是说这个函数没有代码实体,在编译的时候,链接器将无法找到fun2的目标代码从而报错。

    其实有了对虚函数的正确的认识,上面的多级继承是很自然就能明白的。然而在多继承的情况下,情况就有所不同了……

    多继承下虚函数的使用

    假如一个类,它由多个父类继承而来,而在不同的父类的继承体系里,都存在虚函数的时候,这个类的对象布局又会是怎样的?它又是怎样定位虚函数的呢?

    让我们看看下面的代码:
 //Source filename: Win32Con.cpp
 #include <iostream>
 using namespace std;
class parent1
 {
public:
    virtual int fun1(){cout<<"parent1::fun1()"<<endl;return 0;};
};
class parent2
 {
public:
    virtual int fun2(){cout<<"parent2::fun2()"<<endl;return 0;};
};
class child1:public parent1,public parent2
 {
public:
    virtual int fun1()
    {
        cout<<"child1::fun1()"<<endl;
        return 0;
    }
    virtual int fun2()
    {
        cout<<"child1::fun2()"<<endl;
        return 0;
    }
};
void test_func1(parent1 *pp)
{
    pp->fun1();
}
void test_func2(parent2 *pp)
{
    pp->fun2();
}
int main(int argc, char* argv[])
{
    child1 chobj;
    test_func1(&chobj);
    test_func2(&chobj);
    return 0;
}
 


    这一次,我们有两个父类,parent1和parent2,在parent1里定义了虚函数fun1,而在parent2里定义了虚函数fun2,然后我们有一个子类child1,在里面重新实现了fun1和 fun2两个虚函数。然后我们编写了test_func1函数来调用parent1类型对象的fun1函数,编写了test_func2函数调用parent2对象的fun2函数。在main函数里我们实例化了一个child1类型的对象chobj,然后分别传给test_func1和test_func2去执行。

    这段代码的运行结果非常简单就能看出来:

    child1::fun1()

    child1::fun2()

    但是,让我们看看对象布局吧:
 class child1    size(8):
        +---
        | +--- (base class parent1)
 0      | | {vfptr}
        | +---
        | +--- (base class parent2)
 4      | | {vfptr}
        | +---
        +---

child1::$vftable@parent1@:
        | &child1_meta
        |  0
 0      | &child1::fun1

child1::$vftable@parent2@:
        | -4
 0      | &child1::fun2

child1::fun1 this adjustor: 0
child1::fun2 this adjustor: 4

 


    注意到没?在child1的对象布局里,出现了两个vfptr指针!

    这两个虚函数表指针分别继承于parent1和parent2类,分别指向了不同的两个虚函数表。

    问题来了,当我们使用test_func1调用parent1类的fun1函数的时候,调用个过程还比较好理解,可以从传入的地址参数取得继承自parent1的vfptr,从而执行正确的fun1函数代码,但是当我们调用test_func2函数的时候,为什么程序可以自动取得来自parent2的vfptr呢,从而得出正确的fun2函数的地址呢?

    其实,这个工作是编译器自动根据实例的类型完成的,在编译阶段就已经确定了在调用test_func2的时候,传入的this指针需要增加一定的偏移(在这里则是第一个vfptr所占用的大小,也就是4字节)。

    我们可以看看main函数里这部分代码的反汇编代码:
   child1 chobj;
00F5162E 8D 4D F4         lea         ecx,[chobj]
00F51631 E8 F5 FB FF FF   call        child1::child1 (0F5122Bh)
    test_func1(&chobj);
00F51636 8D 45 F4         lea         eax,[chobj]
00F51639 50               push        eax
00F5163A E8 6F FB FF FF   call        test_func1 (0F511AEh)
00F5163F 83 C4 04         add         esp,4
    test_func2(&chobj);
00F51642 8D 45 F4         lea         eax,[chobj]
00F51645 85 C0            test        eax,eax
00F51647 74 0E            je          main+47h (0F51657h)
00F51649 8D 4D F4         lea         ecx,[chobj]
00F5164C 83 C1 04         add         ecx,4
00F5164F 89 8D 2C FF FF FF mov         dword ptr [ebp-0D4h],ecx
00F51655 EB 0A            jmp         main+51h (0F51661h)
00F51657 C7 85 2C FF FF FF 00 00 00 00 mov         dword ptr [ebp-0D4h],0
00F51661 8B 95 2C FF FF FF mov         edx,dword ptr [ebp-0D4h]
00F51667 52               push        edx
00F51668 E8 F6 FA FF FF   call        test_func2 (0F51163h)
00F5166D 83 C4 04         add         esp,4
    return 0;


    从第4行至第5行,执行的是test_func1函数,this指针指向 chobj (第2行lea ecx,[chobj]),但是调用test_func2函数的时候,this指针被增加了4(第14行)!于是,在test_func2执行的时候,就可以从&chobj+4的地方获得vfptr指针,从而根据parent2的对象布局得到了fun2的地址并执行了。

    为了证实这点,我们可以将代码做如下的修改:
     1:  int main(int argc, char* argv[])

    2:  {

    3:      child1 chobj;

    4:      test_func1(&chobj);

    5:      test_func2((parent2 *)(void *)&chobj);

    6:      return 0;

    7:  }

    8:
 


    请注意红色部分的变化,在讲chobj传入给test_func2之前,先用(void *)强制转换为无类型指针,再转换为parent2 指针,这样的转换,显然是可行的,因为chobj本身就是parent2的子类,然而,程序的执行效果却是:

    child1::fun1()

    child1::fun1()

    执行test_func2函数,调用的是parent2::fun2,但是居然执行的是child1::fun1()函数!!!

    这中间发生了些什么呢?我们再看看反汇编的代码:
   child1 chobj;
013D162E 8D 4D F4         lea         ecx,[chobj]
013D1631 E8 F5 FB FF FF   call        child1::child1 (13D122Bh)
    test_func1(&chobj);
013D1636 8D 45 F4         lea         eax,[chobj]
013D1639 50               push        eax
013D163A E8 6F FB FF FF   call        test_func1 (13D11AEh)
013D163F 83 C4 04         add         esp,4
    test_func2((parent2*)(void *)&chobj);
013D1642 8D 45 F4         lea         eax,[chobj]
013D1645 50               push        eax
013D1646 E8 18 FB FF FF   call        test_func2 (13D1163h)
013D164B 83 C4 04         add         esp,4
    return 0;
 


    从调用test_func2的反汇编代码可以看到,这一次ecx寄存器的值没有做改变!所以在执行test_func2的时候,将取得parent1对象布局里的vfptr,而这个vfptr所指的虚函数表里的第一项就是fun1,并且被填写为child1::fun1的地址了。所以才出现了child::fun1的输出内容!显然这里有一个隐藏的致命问题,加入parent1和parent2的第一个虚函数的参数列表不一致,这样的调用显然就会导致堆栈被破坏掉,程序99%会立即崩溃。之前的程序没有崩溃并且成功输出内容,不过是因为parent1::fun1()和parent2::fun2()的参数列表一致的关系而已。

    所以,千万不要在使用一个多继承对象的时候,将其类型信息丢弃,编译器还需要依靠正确的类型信息,在使用虚函数的时候来得到正确的汇编代码!

 

 

posted @ 2009-08-05 17:45 chaosuper 阅读(171) | 评论 (0)编辑 收藏

仅列出标题
共12页: First 3 4 5 6 7 8 9 10 11 Last