【问题标题】:How does C++ store functions and objects in memory? [duplicate]C++如何在内存中存储函数和对象? [复制]
【发布时间】:2012-09-21 13:37:16
【问题描述】:

假设我们有一个类

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) 

我知道其中很多依赖于实现,但如果有人可以对任何实现有所了解或只是提供一个概述,那就太好了。

【问题讨论】:

  • 通过空指针调用方法是未定义的行为。任何事情都可能发生 - 它不必崩溃,但标准允许。
  • 不是 C++ 人,所以这是一个猜测,但是:您的代码不需要访问 A 实例的任何内存。 sayHi() 不使用字段x,而且它不是虚拟的,所以它不需要访问 vtable 来解析。 C++ 编译器实际上必须插入检查以查看 a 是否是导致错误的有效指针。

标签: c++ function turbo-c


【解决方案1】:

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

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

请不要误会,这仍然是未定义的行为。当您调用它时,您的程序可能会选择向您的联系人列表发送讨厌的电子邮件。在这个特定的例子中,它做了最坏的事情,而且似乎有效。

virtual方法案例在我回答问题后已经添加,但我不会细化我的答案,因为它包含在其他人的答案中。

【讨论】:

  • +1 指出“有效”的未定义行为是不好的。
【解决方案2】:

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

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

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

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

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

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

【讨论】:

  • A 类没有虚方法这一事实无关紧要。重要的是被调用的特定函数,sayHi,不是一个虚函数,并且不使用类对象的成员。所以这个函数可以(并且被)调用而不使用指向类对象的指针。
【解决方案3】:

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

当您将此方法设为虚拟时,情况就会发生变化。编译器在编译时不再知道与该方法关联的代码是什么,必须在运行时弄清楚。它所做的是查看一个基本上包含所有虚拟方法的函数指针的表;此表与类实例相关联,因此它会查看与 NULL 指针相关的内存片段,因此在这种情况下会崩溃。

【讨论】:

    【解决方案4】:

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

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

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-04-20
      • 1970-01-01
      • 1970-01-01
      • 2010-09-29
      • 2017-08-27
      • 1970-01-01
      • 2021-04-11
      相关资源
      最近更新 更多