2012-09-30 69 views
9

假设我们有一个类C++如何在内存中存储函数和对象?

class A 
{ 
    int x; 
public: 
    void sayHi() 
    { 
     cout<<"Hi"; 
    } 
}; 

int main() 
{ 
    A *a=NULL; 
    a->sayHi(); 
} 

上面的代码将编译上的Turbo C(其中I测试)和打印Hi作为输出。

我期待崩溃,因为aNULL。更过,如果我做sayHi()功能虚拟,它说

Abnormal temination(Segmentation fault in gcc) 

我知道有很多是依赖于实现的,但如果任何人都可以扔在任何实现一些轻或只是给出一个概述这将是非常好的。

+2

通过空指针调用方法是未定义的行为。任何事情都可能发生 - 它不必崩溃,但标准允许它。 –

+0

不是C++的人,所以这是一个猜测,但是:你的代码不需要访问任何'A'的实例的内存。 'sayHi()'不使用字段'x',它不是虚拟的,因此它不需要访问vtable来解析。 C++编译器实际上必须插入一个检查来查看'a'是否是导致错误的有效指针。 – millimoose

回答

6

在C++中,类的方法不存储在该类的实例中。它们只是一些“特殊”功能,除了程序员指定的参数外,它们还透明地接受指针this

在你的情况下,sayHi()方法没有引用任何类字段,因此,this指针(它是NULL)从未被遵循。

不要误解,但这仍然是未定义的行为。当您调用此程序时,您的程序可能会选择将讨厌的电子邮件发送到您的联系人列表。在这个特定的例子中,它做的最糟糕的事情,似乎工作。

virtual方法案例已被添加,因为我回答了这个问题,但我不会优化我的答案,因为它被其他人的答案所包含。

+1

+1,指出“工作”的未定义行为是坏的。 –

7

显然,代码具有未定义的行为,即无论您得到什么是偶然的。也就是说,系统在调用非虚拟成员函数时不需要知道对象:它只能根据签名调用。此外,如果一个成员函数不需要访问成员,它根本不需要真的需要一个对象并且可以运行。这是代码打印某些输出时所观察到的内容。不管这个系统是如何实现的,都没有定义,但是,没有什么说它可以工作。

当调用一个虚拟函数时,系统开始查看与该对象关联的类型信息记录。在NULL指针上调用虚函数时,不存在这样的信息并尝试访问它可能会导致某种崩溃。尽管如此,它并不是必须的,但它对大多数系统都是如此。

顺便说一下,main()总是返回int

1

如果您调用类的非虚方法,对于编译器来说,只需知道函数属于哪个类并通过解引用(尽管是指向调用方法的类的NULL)即可,编译器会获取那个信息。方法几乎只是一个将指向类实例的指针作为隐藏参数的函数。这个指针是NULL,但如果你没有在方法中引用任何属性,那并不重要。

当您使此方法变为虚拟时,情况会发生变化。编译器不再知道编译时与该方法关联的代码,并且必须在运行时解决这个问题。它的功能是查看一个基本上包含所有虚拟方法的函数指针的表;这个表与类实例相关联,因此它查看相对于NULL指针的内存块,因此在这种情况下会崩溃。

3

作为概括,从一个类实例化的,没有超类和虚拟函数的对象的布局如下:

* - v_ptr ---> * pTypeInfo 
|    |- pVirtualFuncA 
|    |- pVirtualFuncB 
|- MemberVariableA 
|- MemberVariableB 

v_ptr是一个指向v表 - 其中包含的虚拟地址函数和RTTI数据。没有虚函数的类没有v表。

在上例中,class A没有虚拟方法,因此没有v-表。这意味着调用sayHi()的实现可以在编译时确定并且是不变的。

编译器生成的代码将隐含的this指针设置为a,然后跳转到sayHi()的开头。由于该实现不需要该对象的内容,因此当指针为NULL时它的工作原理是一个令人高兴的巧合。

如果您想让sayHi()为虚拟,编译器无法确定在编译器时调用的实现,所以会生成代码来查找v表中函数的地址并调用它。在你的例子中,aNULL,编译器读取地址0的内容,导致中止。

+0

A类没有虚拟方法的事实是不相关的。重要的是被调用的特定函数,比如Hi,不是一个虚函数,并且不使用类对象的成员。所以这个函数可以被调用,而不需要使用指向类对象的指针。 –