精华内容
下载资源
问答
  • 一、虚函数 ·虚是怎么实现的?虚存放在哪里? ·虚中的数据是在什么时候确定的? ·对象中的虚指针又在什么时候赋值的? 我们很难通过 C++语言本身来找到答案。 C++标准给编译器实现者定义了语法规范,...

    https://blog.csdn.net/lingfengtengfei/article/details/12345809 

    一、虚函数

    • ·虚表是怎么实现的?虚表存放在哪里?
    • ·虚表中的数据是在什么时候确定的?
    • ·对象中的虚表指针又在什么时候赋值的?

    我们很难通过 C++语言本身来找到答案。 C++标准给编译器实现者定义了语法规范,但是被并没有定义如何实现这些语法规范,不同的编译器实现者可能有不同的实现方法,可以肯定的是他们的编译器必须符合这些语法规范。汇编语言作为最接近机器语言的计算机语言,可以为我们揭示一些隐藏在编译器内部的细节。接下来本来就试图通过对 C++源码进行反汇编的方式来解答这些疑惑。

    二、分析

    这里我选用 WinXP 和 VS2008 作为我们这次分析的平台。我们建立一个最简单的 Win32 控制台程序,并定义两个简单的类:

    接下来我们可以直接编译这些 C++源码就可以得到相应的汇编代码。 通过分析这些汇编代码我们就找到许多有用的信息。我们可以找到这样的汇编代码:

    以上的汇编代码定义了两个数据段, 而这两个数据段中的内容恰好就是类的虚表。 至此虚表的"庐山真面目"完全展示在我们的面前。 根据这些信息,我们可以推理出很多有用的结论:

    • ·拥有虚函数的类会有一个虚表,而且这个虚表存放在类定义模块的数据段中。模块的数据段通常存放定义在该模块的全局数据和静态数据区,这样我们可以把虚表看作是模块的全局数据或者静态数据
    • ·类的虚表会被这个类的所有对象所共享。类的对象可以有很多,但是他们的虚表指针都指向同一个虚表,从这个意义上说,我们可以把虚表简单理解为类的静态数据成员。值得注意的是,虽然虚表是共享的,但是虚表指针并不是,类的每一个对象有一个属于它自己的虚表指针。
    • ·虚表中存放的是虚函数的地址。

    另外一个大的疑惑就是对象的虚表指针是在什么时候被赋值的? 我们都知道,类的对象是通过构造函数来完成初始化,但是我们从来没有在构造函数中初始化虚表指针, 那么编译器在幕后又做了哪些事情呢? 我们依然还是通过反汇编来找到答案。 在这个控制台程序的 main 函数中我们构建一个类对象:

     

    类的非静态成员函数调用时,编译器会传入一个"隐藏"的参数。 这个参数就是通常我们说的"this"指针,它的值就是对象的地址。 在上面的代码中,寄存器 ECX 保存的就是这个"

    this" 指 针 , 同 时 它 的 值 又 赋 给 了 寄 存 器 EAX。"??_7CD-szBase@@6B@"就是上面提到的虚表,同时它也代表了虚表的地址。

    接下来,虚表的地址被赋给了由寄存器 EAX 指定的内存中。由此可见,虚表的地址被存放在对象的起始位置,即对象的第一个数据成员就是它的虚表指针。 同时我们还可以注意到,虚表指针的初始化确实发生在构造函数的调用过程中, 但是在执行构造函数体之前,即进入到构造函数的"{"和"}"之前。 为了更好的理解这一问题, 我们可以把构造函数的调用过程细分为两个阶段,即:

    1.进入到构造函数体之间。在这个阶段如果存在虚函数的话,虚表指针被初始化。如果存在构造函数的初始化列表的话,初始化列表也会被执行。

    2.进入到构造函数体内。这一阶段是我们通常意义上说的构造函数

     

    简单的搞个基类Base{void fun();virtual void print(){...};public:int a;static b;}

    定义一个对象 B b;调试状态下就可以看到b包含了什么

    类中只有虚表指针和普通成员(包括const成员)而普通函数,静态成员是不在类中的.

     

    展开全文
  • 虚函数表存在位置,初始化时机

    千次阅读 2014-09-22 15:05:15
    1.虚函数 ·虚是怎么实现的?虚存放在哪里? ·虚中的数据是在什么时候确定的? ·对象中的虚指针又在什么时候赋值的? 我们很难通过 C++语言本身来找到答案。 C++标准给编译器实现者定义了...

    转自:http://blog.csdn.net/lingfengtengfei/article/details/12345809

    1.虚函数

    ·虚表是怎么实现的?虚表存放在哪里?

    ·虚表中的数据是在什么时候确定的?

    ·对象中的虚表指针又在什么时候赋值的?

    我们很难通过 C++语言本身来找到答案。 C++标准给编译器实现者定义了语法规范,但是被并没有定义如何实现这些语法规范,不同的编译器实现者可能有不同的实现方法,可以肯定的是他们的编译器必须符合这些语法规范。汇编语言作为最接近机器语言的计算机语言,可以为我们揭示一些隐藏在编译器内部的细节。接下来本来就试图通过对 C++源码进行反汇编的方式来解答这些疑惑。

    二、分析

    这里我选用 WinXP 和 VS2008 作为我们这次分析的平台。我们建立一个最简单的 Win32 控制台程序,并定义两个简单的类:


    接下来我们可以直接编译这些 C++源码就可以得到相应的汇编代码。 通过分析这些汇编代码我们就找到许多有用的信息。我们可以找到这样的汇编代码:


    以上的汇编代码定义了两个数据段, 而这两个数据段中的内容恰好就是类的虚表。 至此虚表的"庐山真面目"完全展示在我们的面前。 根据这些信息,我们可以推理出很多有用的结论:

    ·拥有虚函数的类会有一个虚表,而且这个虚表存放在类定义模块的数据段中。模块的数据段通常存放定义在该模块的全局数据和静态数据,这样我们可以把虚表看作是模块的全局数据或者静态数据

    ·类的虚表会被这个类的所有对象所共享。类的对象可以有很多,但是他们的虚表指针都指向同一个虚表,从这个意义上说,我们可以把虚表简单理解为类的静态数据成员。值得注意的是,虽然虚表是共享的,但是虚表指针并不是,类的每一个对象有一个属于它自己的虚表指针。

    ·虚表中存放的是虚函数的地址。

    另外一个大的疑惑就是对象的虚表指针是在什么时候被赋值的? 我们都知道,类的对象是通过构造函数来完成初始化,但是我们从来没有在构造函数中初始化虚表指针, 那么编译器在幕后又做了哪些事情呢? 我们依然还是通过反汇编来找到答案。 在这个控制台程序的 main 函数中我们构建一个类对象:

     

    类的非静态成员函数调用时,编译器会传入一个"隐藏"的参数。 这个参数就是通常我们说的"this"指针,它的值就是对象的地址。 在上面的代码中,寄存器 ECX 保存的就是这个"

    this" 指 针 , 同 时 它 的 值 又 赋 给 了 寄 存 器 EAX。"??_7CD-szBase@@6B@"就是上面提到的虚表,同时它也代表了虚表的地址。

    接下来,虚表的地址被赋给了由寄存器 EAX 指定的内存中。由此可见,虚表的地址被存放在对象的起始位置,即对象的第一个数据成员就是它的虚表指针。 同时我们还可以注意到,虚表指针的初始化确实发生在构造函数的调用过程中, 但是在执行构造函数体之前,即进入到构造函数的"{"和"}"之前。 为了更好的理解这一问题, 我们可以把构造函数的调用过程细分为两个阶段,即:

    1.进入到构造函数体之间。在这个阶段如果存在虚函数的话,虚表指针被初始化。如果存在构造函数的初始化列表的话,初始化列表也会被执行。

    2.进入到构造函数体内。这一阶段是我们通常意义上说的构造函数

     

    简单的搞个基类Base{void fun();virtual void print(){...};public:int a;static b;}

    定义一个对象 B b;调试状态下就可以看到b包含了什么

    类中只有虚表指针和普通成员(包括const成员)而普通函数,静态成员是不在类中的.

     





     

    展开全文
  • 虚函数表存储位置

    2008-04-03 14:32:00
    那么应当将虚函数表存在什么地方呢?大多数编译器采取如下的策略:将虚函数表存储在其类的Object File中,并且该Object File拥有第一个非内联、非纯虚的虚函数的定义。注意:一个类可能有多个Object File, 
    显然,一个类的所有对象拥有的虚函数都应该相同,不同的只是调用虚函数时传入的对象指针(this指针),所以应该是一个类一个虚函数表。
    那么应当将虚函数表存在什么地方呢?大多数编译器采取如下的策略:将虚函数表存储在其类的Object File中,并且该Object File拥有第一个非内联、非纯虚的虚函数的定义。注意:一个类可能有多个Object File,
     
    展开全文
  • 之前我们写过c++之虚函数表及多态,讨论了存在虚函数时的虚函数表变化及继承关系。...在c++基类中,如果存在虚函数,那么它就会有一个指向虚函数表的指针vptr(vptr具体在什么位置,与编译器有关,大多数...

    之前我们写过c++之虚函数表及多态,讨论了存在虚函数时的虚函数表变化及继承关系。今天讨论一下类的对象的存储方式。其中参考C++类对象的内存结构

    一、类对象内存结构

    因为静态成员属于整个类,在类实例化对象之前就已经分配空间了,而类的非静态成员必须在类实例化对象后才有内存空间

    在c++基类中,如果存在虚函数,那么它就会有一个指向虚函数表的指针vptr(vptr具体在什么位置,与编译器有关,大多数都在开始处),之后是类的成员的内存数据。而对于子类,最开始的内存数据记录着父类对象的拷贝(包括父类虚函数表指针和成员变量),之后才是自己的成员的内存数据。至于虚函数表的变化,可以参考c++之虚函数表及多态
    在这里插入图片描述
    例如存在一个虚函数继承:

    class Base 
    { 
    public: 
        virtual void f() { cout << “Base::f” << endl; } 
        virtual void g() { cout << “Base::g” << endl; } 
        virtual void h() { cout << “Base::h” << endl; } 
        int base; 
    protected: 
    private: 
    }; 
    class Child2 : public Base 
    { 
    public: 
        virtual void f() { cout << “Child2::f” << endl; } 
        virtual void g1() { cout << “Child2::g1” << endl; } 
        virtual void h1() { cout << “Child2::h1” << endl; } 
        int child2; 
    protected: 
    private: 
    }; 
    

    在这里插入图片描述
    二、对象不能实现多态

    在这里插入图片描述
    在这里插入图片描述

    #include<iostream>
    #include<string>
    
    using namespace std;
    
    class base
    {
    public:
    	virtual ~base() { cout << "析构基类" << endl; };
    	virtual string GetName() { return "base"; }
    	void GetA() { cout << "Base::GetA" << endl; };
    	int a=1;
    };
    
    class derived : public base
    {
    public:
    	virtual ~derived() { cout << "析构子类" << endl; };
    	virtual string GetName() { return "derived"; }
    	void GetB() { cout << "Derived::GetB" << endl; };
    	int a = 100;
    	int b = 200;
    };
    
    int main()
    {
    	base B1;
    	base *B2 = new base();
    	derived D1;
    
    	//子类对象可以给父类对象赋值,而父类对象不能给子类对象赋值
    	B1 = D1;
    	B1.GetA();
    	cout << B1.GetName() << endl;
    	//即使赋值操作,基类的a并没有被子类的a所覆盖。
    	cout << "a = " << B1.a << endl;
    	//delete[] B1;
    
    	B2 = new derived();
    	B2->GetA();
    	//只有这一个体现了多态(虚函数继承),指针和引用才能实现多态。
    	cout<<B2->GetName()<<endl;
    	cout << "a = " << B2->a << endl;
    	delete B2;
    	
    	system("pause");
    }
    

    在这里插入图片描述
    结论:无论赋值操作还是赋值构造时,一个类对象里面的vptr永远不会变,永远都会指向 所属类 的虚函数表。而当使用指针或者引用时,vptr会指向 继承类 的虚函数表。

    展开全文
  • C++中必须知道的问题

    2014-01-03 15:41:40
    把前段时间学习c++中遇到的一些问题跟大家分享一下 1.静态成员函数为什么不能声明为const ...6.虚函数表是怎么回事 7.虚函数指针vptr存在于一个对象的什么位置,其作用是什么 8.一个string对象的
  • 13.10 被继承的虚函数仍然是虚函数 223 13.11 系统是如何调用虚函数的 224 13.12 在虚函数中使用成员名限定 224 13.13 虚析构函数 225 第14章 数组 228 14.1 数组的基本用法 228 14.1.1 什么是数组 228 ...
  • 2.1.10 存在正交归一化离散值函数的完备集合吗? 57 2.2 哈尔、沃尔什和哈达玛变换 57 2.2.1 哈尔函数是如何定义的? 57 2.2.2 沃尔什函数是如何定义的? 57 B2.4 用拉德马赫函数定义的沃尔什函数 58 2.2.3 ...
  • 继承来的虚函数. 她的目标平台是Lua 5.0 ,不能支持Lua 4.0 . 她利用模板原编程技术实现.这意味着,你不需要额外的预处理过程去编译你的工程(编译器 会替你完成全部的工作).这还意味着,你也不需要(通常)知道你注册的...
  • 2.1.6 什么函数不能声明为虚函数? 2.1.7 冒泡排序算法的时间复杂度是什么? 2.1.8 写出float x 与“零值”比较的if语句 2.1.9 Internet采用哪种网络协议?该协议的主要层次结构? 2.2.0 Internet物理地址和IP...
  • 9.6.3 使用引用处理虚函数 469 9.6.4 纯虚函数 470 9.6.5 抽象类 471 9.6.6 间接基类 474 9.6.7 虚析构函数 476 9.7 类类型之间的强制转换 481 9.8 嵌套类 482 9.9 C++/CLI编程 485 9.9.1 C++/CLI...
  • 面向对象的编程语言与以往各种编程语言有根本的不同,它设计的出发点就是为了能更直接的描述客观世界中存在的事物以及它们之间的关系。面向对象的编程语言将客观事物看作具有属性和行为的对象,通过抽象找出同一类...
  • 声明方法的存在而不去实现它的类被叫做抽象类(abstract class),它用于要创建一个体现某些基本行为的类,并为该类声明方法,但不能在该类中实现该类的情况。不能创建abstract 类的实例。然而可以创建一个变量,其...
  • 2.5.5 虚函数 33 2.6 面向对象的程序设计 33 2.6.1 具体类型的问题 33 2.6.2 类层次结构 34 2.7 通用型程序设计 36 2.7.1 容器 36 2.7.2 通用型算法 37 2.8 附言 38 2.9 忠告 39 第3章 标准库概览 40 3.1 ...
  • 2.5.5 虚函数 33 2.6 面向对象的程序设计 33 2.6.1 具体类型的问题 33 2.6.2 类层次结构 34 2.7 通用型程序设计 36 2.7.1 容器 36 2.7.2 通用型算法 37 2.8 附言 38 2.9 忠告 39 第3章 标准库概览 40 3.1 ...
  • C++程序设计语言(特别版)--源代码

    热门讨论 2012-04-23 07:33:51
    2.5.5 虚函数 33 2.6 面向对象的程序设计 33 2.6.1 具体类型的问题 33 2.6.2 类层次结构 34 2.7 通用型程序设计 36 2.7.1 容器 36 2.7.2 通用型算法 37 2.8 附言 38 2.9 忠告 39 第3章 标准库概览 40 3.1 ...
  • oracle数据库经典题目

    2011-02-17 15:05:20
    19.视图是一个表示的数据的数据库对象,它允许用户从一个或一组中通过一定的查询语句建立一个“虚表”。 20.序列是一种可被多个用户使用的用于产生一系列唯一数字的数据库对象。尤其适合多用户环境中,可以...
  • PT80-NEAT开发指南v1.1

    2014-06-24 18:38:34
    NEAT 开 发 指南 文档 适用于 PT80 系列 移动数据终端 版本记录 版本号 版本描述 发布日期 V 1.0 初始版本。 2012-04-12 V1.1 修改前三章内容 2012-09-25 目录 第一章 关于本手册.....................................
  • c++ 面试题 总结

    2009-09-16 08:44:40
    同一个函数存在一个实体(inline除外) 子类覆盖它的函数不加virtual ,也能实现多态。 在子类的空间里,有父类的私有变量。私有变量不能直接访问。 ----------------------------------------------------------...
  • Visual C++编程技巧精选500例.pdf

    热门讨论 2012-09-01 15:01:50
    288 如何使用declspec(dllexport)导出DLL函数? 第15章 程序版权信息 289 如何查询程序说明? 290 如何查询程序开发商? 291 如何查询程序内部名称? 292 如何查询程序产品名称? 293 如何查询程序关联注释? 294 如何查询...
  • 翻译的过程中,译者感到此言不:作者从数据库的基本概念到数据库建模,从如何运用规范化原则到如何做成实际的数据库,从如何保护数据库完整性到如何提高数据库的性能,从数据库的安全机制到并发事务控制,从...
  • 翻译的过程中,译者感到此言不:作者从数据库的基本概念到数据库建模,从如何运用规范化原则到如何做成实际的数据库,从如何保护数据库完整性到如何提高数据库的性能,从数据库的安全机制到并发事务控制,从...
  • 疯狂JAVA讲义

    2014-10-17 13:35:01
    学生提问:当我们使用编译C程序时,不仅需要指定存放目标文件的位置,也需要指定目标文件的文件名,这里使用javac编译Java程序时怎么不需要指定目标文件的文件名呢? 13 1.5.3 运行Java程序 14 1.5.4 根据...
  • 函数级注解会详细到重点行,甚至每一行, 例如申请互斥锁的主体函数,不可谓不重要,而官方注释仅有一行,如图所示 另外画了一些字符图方便理解,直接嵌入到头文件中,比如虚拟内存的全景图,因没有这些图是很难理解...

空空如也

空空如也

1 2
收藏数 25
精华内容 10
关键字:

虚函数表存在什么位置