请教下__declspec(novtable),虚函数调用的问题

dllg1988 2013-11-18 12:16:55

class __declspec(novtable) A
{
public:
A(){}
virtual void Test(){}
virtual void Test2(){}
};

class __declspec(novtable) B : public A
{
public:
B(){}
virtual void Test(){}
virtual void Test3(){Test2();}
};

int main()
{
B b;
b.Test();
b.Test3();

getchar();
return 0;
}

这段代码会崩溃,类A和B经过__declspec(novtable)修饰后不会产生虚表,也不会初始化虚指针,但main里是通过对象来调用函数,《深度探索C++模型》中说“经一个由class object调用的virtual function,这种操作总是被编译器像对待一般的nonstatic member function一样加以决议”,那就是说跟虚函数虚表没有什么关系,可是为什么还会崩溃呢。如果main里像这样调用
A *pA = new B;
pA->Test();
这样崩溃了还能理解,求大神指点下
...全文
200 8 打赏 收藏 转发到动态 举报
写回复
用AI写文章
8 条回复
切换为时间正序
请发表友善的回复…
发表回复
JiMoKuangXiangQu 2013-11-18
  • 打赏
  • 举报
回复
参考上面的文字,再结合c++对象模型,相信LZ不难理解了,呵呵。
JiMoKuangXiangQu 2013-11-18
  • 打赏
  • 举报
回复
可参考下面的这段关于novtable说明(摘自MSDN):
This is a __declspec extended attribute. 

This form of __declspec can be applied to any class declaration, but should only be applied to pure interface classes, that is, classes that will never be instantiated on their own. The __declspec stops the compiler from generating code to initialize the vfptr in the constructor(s) and destructor of the class. In many cases, this removes the only references to the vtable that are associated with the class and, thus, the linker will remove it. Using this form of __declspec can result in a significant reduction in code size.

If you attempt to instantiate a class marked with novtable and then access a class member, you will receive an access violation (AV).
JiMoKuangXiangQu 2013-11-18
  • 打赏
  • 举报
回复
引用 7 楼 dllg1988 的回复:
[quote=引用 2 楼 JiMoKuangXiangQu 的回复:] 参考上面的文字,再结合c++对象模型,相信LZ不难理解了,呵呵。
多谢。对于b.Test3();这句会崩溃还是不太明白,把问题简化下

class __declspec(novtable) A 
{ 
public: 
	A(){} 
	virtual void Test(){} 
	virtual void Test2(){Test();} 
};

A a;
a.Test(); //(1)OK
a.Test2();//(2)Error

A &a1 = a;
a1.Test(); (3)//Error;

A *pA = &a;
pA->Test();(4)//Error;
4个调用只有(1)可以,那就是说后面3个调用都涉及到虚指针和虚表,因为有__declspec(novtable)修饰,虚表没有构建,虚指针没有初始化所以违规访问了。(3)和(4)还能理解下,(2)有点不太明白,难道在虚函数里再调用另一个虚函数一定会涉及到虚表/虚指针,我预期的是编译器会把这2个调用都当作nonstatic member function来处理,看来是我理解错了。 或者换个问题,对于一个虚函数,在什么情况下编译器会把它当作nonstatic member function一样来处理,什么时候会通过虚表虚指针来处理? 另多谢建议,不懂汇编,对C++理解也不够深,书是看了几遍,但依旧没完全看懂[/quote] c++ 设计 virtual 的目的,就是为了实现多态,那引起多态发生也是有条件的: 指针和引用,这个可以帮助你理解(3)(4)。 (2)呢,其实也不难理解,回到virtual 的目的,是为了实现多态。假定当前有个类B继承了A, 同时实现了函数Test()。 然后有: B b; A *a = &b; a->Test2(); //那么此时A::Test2()应该调用B::Test() 所以调用了虚函数的成员函数也应该使用虚指针调用,才可得出正确结果。 看增加了一个非虚函数Test3()的代码,虚函数对非虚函数的调用是可以的。
class __declspec(novtable) A 
{ 
public: 
	A(){} 
	virtual void Test(){} 
	virtual void Test2(){/*Test*/Test3();} 
	void Test3() {}
};

int main()
{
	A a;
	a.Test(); //(1)OK
	a.Test2();//(2)OK

	return 0;
}
dllg1988 2013-11-18
  • 打赏
  • 举报
回复
引用 2 楼 JiMoKuangXiangQu 的回复:
参考上面的文字,再结合c++对象模型,相信LZ不难理解了,呵呵。
多谢。对于b.Test3();这句会崩溃还是不太明白,把问题简化下

class __declspec(novtable) A 
{ 
public: 
	A(){} 
	virtual void Test(){} 
	virtual void Test2(){Test();} 
};

A a;
a.Test(); //(1)OK
a.Test2();//(2)Error

A &a1 = a;
a1.Test(); (3)//Error;

A *pA = &a;
pA->Test();(4)//Error;
4个调用只有(1)可以,那就是说后面3个调用都涉及到虚指针和虚表,因为有__declspec(novtable)修饰,虚表没有构建,虚指针没有初始化所以违规访问了。(3)和(4)还能理解下,(2)有点不太明白,难道在虚函数里再调用另一个虚函数一定会涉及到虚表/虚指针,我预期的是编译器会把这2个调用都当作nonstatic member function来处理,看来是我理解错了。 或者换个问题,对于一个虚函数,在什么情况下编译器会把它当作nonstatic member function一样来处理,什么时候会通过虚表虚指针来处理? 另多谢建议,不懂汇编,对C++理解也不够深,书是看了几遍,但依旧没完全看懂
JiMoKuangXiangQu 2013-11-18
  • 打赏
  • 举报
回复
LZ对MSDN的那段话的理解似乎有点偏差。原意应该是: “只应该用于那些不能实例化的抽象类。” 后面还有比较关键的是: “该声明阻止编译器生成在类的构造和析构函数内初始化虚表指针的代码”。 就是说用了该声明的类的虚表指针不会被初始化! 举个例子,如果有如下代码:
class /*__declspec(novtable)*/ A 
{ 
public: 
	A(){} 
	virtual void Test(){} 
	virtual void Test2(){} 
};
编译器会在该类的构造函数里面插入类似如下的伪代码: A.vfptr = A.vtab; 当然也有个虚表生成: void *A.vtab[] = {A::Test, A::Test2}; 但当类声明成这样:
class __declspec(novtable) A 
{ 
public: 
	A(){} 
	virtual void Test(){} 
	virtual void Test2(){} 
};
A.vfptr = A.vtab; 这句代码就不会在生成,甚至在大多时候,Linker看到A.vtab没被引用, 认为是dead code,就会把它移调,以减少代码占用空间。 回到LZ的情况: 只要A,B任意一个类不使用novtable声明,B.vfptr都会有值,程序都能运行,但未必是你要的情况。 具体来说, 当 A 用 novtable 而 B 不用,B.vfptr = B.vfptr; 当 A 不用而 B 用 novtable,B.vfptr = A.vfptr; 当 A,B 都用 novtable, B.vfptr不被初始化。 以上分析,仅供参考,LZ可以自己验证。 小小建议,LZ 如果是初次看 C++ 对象模型剖析之类的书,建议动态查看类的反汇编代码,自己有了 心得,再去看书。这样就变成了验证,修谬,掌握了主动,印象深刻,较易记住。否则被动追书,将会 很吃力,也很容易忘记。
dllg1988 2013-11-18
  • 打赏
  • 举报
回复
引用 3 楼 worldy 的回复:
__declspec(novtable) 别随便乱用
从没用过,只是最近在看ATL时才知道有这个玩意儿,就想知道一个类用这玩意儿修饰后编译器做了啥,这个类有啥变化的。
dllg1988 2013-11-18
  • 打赏
  • 举报
回复
引用 2 楼 JiMoKuangXiangQu 的回复:
参考上面的文字,再结合c++对象模型,相信LZ不难理解了,呵呵。
不好意思能否再指点下,鄙人比较愚钝。那段MSDN上的文字之前也看过,但不知道是我理解错了还是。。感觉不太对。MSDN上说常用于抽象类,不能自己实例化,这应该是不对的,上述代码已经证明了。 至于最后这句“If you attempt to instantiate a class marked with novtable and then access a class member, you will receive an access violation (AV).”,MSDN上还给出了例子,通过指针来访问member确实崩溃了,但这里应该是通过虚函数指针和虚表来访问,有违规访问是可以理解的。通过对象来访问,比如b.Test3(); 这样也发生了access violation,有点想不明白。
worldy 2013-11-18
  • 打赏
  • 举报
回复
__declspec(novtable) 别随便乱用

64,281

社区成员

发帖
与我相关
我的任务
社区描述
C++ 语言相关问题讨论,技术干货分享,前沿动态等
c++ 技术论坛(原bbs)
社区管理员
  • C++ 语言社区
  • encoderlee
  • paschen
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
  1. 请不要发布与C++技术无关的贴子
  2. 请不要发布与技术无关的招聘、广告的帖子
  3. 请尽可能的描述清楚你的问题,如果涉及到代码请尽可能的格式化一下

试试用AI创作助手写篇文章吧