C ++如何在内存中存储函数和对象?

时间:2012-09-30 11:19:44

标签: c++ function turbo-c

让我们说我们有一个班级

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

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

以上代码将在Turbo C(我测试过)上编译并打印Hi作为输出。

由于aNULL,我原本期待崩溃。更重要的是,如果我将sayHi()函数设为虚拟,它会说

Abnormal temination(Segmentation fault in gcc) 

我知道很多都是依赖于实现的,但是如果有人可以对任何实现有所了解,或者只是给出一个概述,那将非常好。

4 个答案:

答案 0 :(得分:7)

显然,代码具有未定义的行为,即,无论你获得什么是偶然的。也就是说,系统在调用非虚拟成员函数时不需要知道对象:可以根据签名调用它。此外,如果成员函数不需要访问成员,则根本不需要对象,只需运行即可。这是您在代码打印一些输出时观察到的。然而,这是否是系统的实现方式尚未定义,即没有任何说它有效。

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

BTW,main() 始终返回int

答案 1 :(得分:6)

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

在您的情况下,sayHi()方法不引用任何类字段,因此,this指针(NULL)永远不会被跟踪。

毫无疑问,这仍然是未定义的行为。当您调用此程序时,您的程序可能会选择将讨厌的电子邮件发送到您的联系人列表。在这个特定的例子中,它做了最糟糕的事情,似乎有效。

自我回答问题以来,virtual方法案例已被添加,但我不会改进我的答案,因为它已包含在其他人的答案中。

答案 2 :(得分:4)

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

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

v_ptr是指向v表的指针 - 包含虚拟函数的地址和对象的RTTI数据。没有虚函数的类没有v表。

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

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

如果要使sayHi()为虚拟,则编译器无法确定在编译器时调用的实现,因此生成代码以查找v表中函数的地址并调用它。在aNULL的示例中,编译器会读取地址0的内容,从而导致中止。

答案 3 :(得分:1)

如果你调用一个类的非虚方法,对于编译器就足以知道函数属于哪个类,并通过解除引用 - 尽管是一个NULL - 指向类的指针来调用该方法,编译器得到了信息。 sayHi()方法几乎只是一个将指向类实例的指针作为隐藏参数的函数。此指针为NULL,但如果您不引用方法中的任何属性,则无关紧要。

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