其实这是我前一段时间思考过的一个问题,是在看《深入探索C++对象模型》这本书的时候我产生的一个疑问,最近在网上又看到类似的帖子,贴出来看看:

我看到了很多有意思的答案,都回答的比较好,下面贴出一些具有代表性的:

Answer 1:

Answer 2:

我们都知道,虚函数是多态机制的基础,就是在程序在运行期根据调用的对象来判断具体调用哪个函数,现在我们来说说它的具体实现原理,主要说一下我自己的理解,如果有什么不对的地方请指正
在每个包含有虚函数的类的对象的最前面(是指这个对象对象内存布局的最前面,至于为什么是最前面,说来话长,这里就不说了,主要是考虑到效率问题)都有一个称之为虚函数指针(vptr)的东西指向虚函数表(vtbl),这个虚函数表(这里仅讨论最简单的单一继承的情况,若果是多重继承,可能存在多个虚函数表)里面存放了这个类里面所有虚函数的指针,当我们要调用里面的函数时通过查找这个虚函数表来找到对应的虚函数,这就是虚函数的实现原理。这里我假设大家都了解了,如果不了解可以去查下资料。好了,既然我们知道了虚函数的实现原理,虚函数指针vptr指向虚函数表vtbl,而且vptr又在对象的最前面,那么我们很容易可以得到虚函数表的地址,下面我写了一段代码测试了一下:

#include <iostream>
#include <stdio.h>
typedef void (*fun_pointer)(void);
using namespace std;
class Test
{
public:
Test()
{
cout<<"Test()."<<endl;
}
virtual void print()
{
cout<<"Test::Virtual void print1()."<<endl;
}
virtual void print2()
{
cout<<"Test::virtual void print2()."<<endl;
}
};
class TestDrived:public Test
{
public:
static int var;
TestDrived()
{
cout<<"TestDrived()."<<endl;
}
virtual void print()
{
cout<<"TestDrived::virtual void print1()."<<endl;
}
virtual void print2()
{
cout<<"TestDrived::virtual void print2()."<<endl;
}
void GetVtblAddress()
{
cout<<"vtbl address:"<<(int*)this<<endl;
}
void GetFirstVtblFunctionAddress()
{
cout<<"First vbtl funtion address:"<<(int*)*(int*)this+ << endl;
}
void GetSecondVtblFunctionAddress()
{
cout<<"Second vbtl funtion address:"<<(int*)*(int*)this+ << endl;
}
void CallFirstVtblFunction()
{
fun = (fun_pointer)* ( (int*) *(int*)this+ );
cout<<"CallFirstVbtlFunction:"<<endl;
fun();
}
void CallSecondVtblFunction()
{
fun = (fun_pointer)* ( (int*) *(int*)this+ );
cout<<"CallSecondVbtlFunction:"<<endl;
fun();
}
private:
fun_pointer fun;
};
int TestDrived::var = ;
int main()
{
cout<<"sizeof(int):"<<sizeof(int)<<"sizeof(int*)"<<sizeof(int*)<<endl;
fun_pointer fun = NULL;
TestDrived a;
a.GetVtblAddress();
cout<<"The var's address is:"<<&TestDrived::var<<endl;
a.GetFirstVtblFunctionAddress();
a.GetSecondVtblFunctionAddress();
a.CallFirstVtblFunction();
a.CallSecondVtblFunction();
return ;
}
 

这里我们通过得到虚函数表的地址调用了里面的虚函数。

这几天又查了下资料,终于搞清楚虚函数表vtable在Linux/Unix中存放在可执行文件的只读数据段中(rodata),这与微软的编译器将虚函数表存放在常量段存在一些差别。将上面的文件编译生成最终的可执行文件,然后利用命令:
objdump -s -x  -d a.out | c++filt | grep "vtable" 可以得到以下输出

上面已经很清楚了这两个类Test和TestDrived都存放在.rodata内,至于上面这条命令,稍微解释以下,objdump可以读取
可执行文件中的详细信息,包括可执行文件的header, section, symbol等等,用objdump获得了可执行文件的符号很多都是
我们看不懂的,或者说与我们源代码中的函数或者变量不太一样,这是因为C++支持函数重载,C++对所有的符号都做了
修饰,很多资料称之为“函数签名”或者“符号修饰”类似的概念,但是我们要将其转换为我们源代码中的符号,这就要用到
c++filt命令了,好了,到这里告一段落了,总之关于虚函数表的具体细节就介绍到这里。

几个值得注意的问题

  1. 虚函数表是class specific的,也就是针对一个类来说的,这里有点像一个类里面的staic成员变量,即它是属于一个类所有对象的,不是属于某一个对象特有的,是一个类所有对象共有的。
  2. 虚函数表是编译器来选择实现的,编译器的种类不同,可能实现方式不一样,就像前面我们说的vptr在一个对象的最前面,但是也有其他实现方式,不过目前gcc 和微软的编译器都是将vptr放在对象内存布局的最前面。
  3. 虽然我们知道vptr指向虚函数表,那么虚函数表具体存放在内存哪个位置呢,虽然这里我们已经可以得到虚函数表的地址。实际上虚函数指针是在构造函数执行时初始化的,而虚函数表是存放在可执行文件中的。下面的一篇博客测试了微软的编译器将虚函数表存放在了目标文件或者可执行文件的常量段中,http://blog.csdn.net/vicness/article/details/3962767,不过我在gcc下的汇编文件中没有找到vtbl的具体存放位置,主要是对可执行文件的装载和运行原理还没有深刻的理解,相信不久有了这些知识之后会很轻松的找到虚函数表到底存放在目标文件的哪一个段中。
  4. 经过测试,在gcc编译器的实现中虚函数表vtable存放在可执行文件的只读数据段.rodata中。
参考文献:
1. http://blog.csdn.net/vicness/article/details/3962767
2. 深度探索c++对象模型

关于C++中虚函数表存放位置的思考的更多相关文章

  1. C++多态中虚函数表合并与继承问题

    多态: C++的多态是通过一张虚函数表(Virtual Table)来实现的,简称为 V-Table.在这个表中,主要是一个类的虚函数的地址表,这张表解决了继承.覆写的问题,保证其真实反应实际的函数. ...

  2. 我理解的C++虚函数表

    今天拜读了陈皓的C++ 虚函数表解析的文章,感觉对C++的继承和多态又有了点认识,这里写下自己的理解.如果哪里不对的,欢迎指正.如果对于C++虚函数表还没了解的话,请先拜读下陈皓的C++ 虚函数表解析 ...

  3. C++ 类的存储方式以及虚函数表

    一.C++成员函数在内存中的存储方式 用类去定义对象时,系统会为每一个对象分配存储空间.如果一个类包括了数据和函数,要分别为数据和函数的代码分配存储空间.按理说,如果用同一个类定义了10个对象,那么就 ...

  4. C++虚函数表和对象存储

    C++虚函数表和对象存储 C++中的虚函数实现了多态的机制,也就是用父类型指针指向其子类的实例,然后通过父类的指针调用实际子类的成员函数,这种技术可以让父类的指针有"多种形态",这 ...

  5. 从零开始学C++之虚函数与多态(一):虚函数表指针、虚析构函数、object slicing与虚函数

    一.多态 多态性是面向对象程序设计的重要特征之一. 多态性是指发出同样的消息被不同类型的对象接收时有可能导致完全不同的行为. 多态的实现: 函数重载 运算符重载 模板 虚函数 (1).静态绑定与动态绑 ...

  6. c++基础之虚函数表指针和虚函数表创建时机

    虚函数表指针 虚函数表指针随对象走,它发生在对象运行期,当对象创建的时候,虚函数表表指针位于该对象所在内存的最前面. 使用虚函数时,虚函数表指针指向虚函数表中的函数地址即可实现多态. 虚函数表 虚函数 ...

  7. C++虚函数和虚函数表

    前导 在上面的博文中描述了基类中存在虚函数时,基类和派生类中虚函数表的结构. 在派生类也定义了虚函数时,函数表又是怎样的结构呢? 先看下面的示例代码: #include <iostream> ...

  8. (C/C++学习)4.C++类中的虚函数表Virtual Table

    说明:C++的多态是通过一张虚函数表(Virtual Table)来实现的,简称为V-Table.在这个表中,主要为一个类的虚函数的地址表,这张表解决了继承.覆写的问题,保证其真实反应实际的虚函数调用 ...

  9. C++ 中的虚函数表及虚函数执行原理

    为了实现虚函数,C++ 使用了虚函数表来达到延迟绑定的目的.虚函数表在动态/延迟绑定行为中用于查询调用的函数. 尽管要描述清楚虚函数表的机制会多费点口舌,但其实其本身还是比较简单的. 首先,每个包含虚 ...

随机推荐

  1. validform 怎么验证小数。

    <input type="text" class="input-text" value="{$info.score}" placeho ...

  2. screen 对象当当获取屏幕而已 innethtml可以知道调整屏幕宽度

    <div id='lidepeng' style="height: 100px;width: 100px;"></div><script type=& ...

  3. 【LeetCode】26. Remove Duplicates from Sorted Array

    Given a sorted array, remove the duplicates in place such that each element appear only once and ret ...

  4. reshape2 数据操作 数据融合( cast)

    我们在做数据分析的时候,对数据进行操作也是一项极其重要的内容,这里我们同样介绍强大包reshape2,其中的几个函数,对数据进行操作cast和melt两个函数绝对少不了. 首先是cast,把长型数据转 ...

  5. LeetCode OJ 54. Spiral Matrix

    Given a matrix of m x n elements (m rows, n columns), return all elements of the matrix in spiral or ...

  6. StretchBlt 提高图片显示质量的方法

    通过SetStretchMode(STRETCH_HALFTONE)实现,同时注意重新设置画刷起点 LRESULT CMainDlg::OnPaint(UINT uMsg, WPARAM wParam ...

  7. 设正整数n的十进制表示为n=ak……a1a0(0<=ai<=9,0<=i<=k,ak!=0),n的个位为起始数字的数字的正负交错之和T(n)=a0+a1+……+(-1)kak,证明:11|n的充分必要条件是11|T(n);(整除理论1.1.2))

    设正整数n的十进制表示为n=ak……a1a0(0<=ai<=9,0<=i<=k,ak!=0),n的个位为起始数字的数字的正负交错之和T(n)=a0+a1+……+(-1)kak, ...

  8. submit提交表单后,不刷新当前页面

    <form method="get" target="test" action="a.html"> <input type ...

  9. 网页 JavaScript的DOM操作

    今天,我首先对之前学习的内容进行了复习,然后学习了当鼠标指向某一个按钮时,切换对应的背景图片. <div id="d1"> </div> <input ...

  10. Tomcat7调优及JVM性能优化for Linux环境

    标签: tomcat7 jvm 性能 分享到: 出处:http://www.iteye.com 该优化针对Linux X86_X64环境 1. Tomcat优化其实就是对server.xml优化(开户 ...