C++如何在内存中存储函数和对象?[副本]

uajslkp6  于 2023-05-20  发布在  其他
关注(0)|答案(4)|浏览(128)

此问题已在此处有答案

What will happen when I call a member function on a NULL object pointer? [duplicate](6个回答)
5年前关闭。
假设我们有一节课

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)

我知道很多都是依赖于实现的,但如果有人能对任何实现做一些说明,或者只是给予一个概述,那就太好了。

pu3pd22g

pu3pd22g1#

很明显,代码有未定义的行为,也就是说,你得到的任何东西都是偶然的。也就是说,系统在调用非虚成员函数时不需要知道对象:只能根据签名调用它。此外,如果一个成员函数不需要访问一个成员,它根本不需要真正需要一个对象,可以直接运行。这是在代码打印一些输出时观察到的。然而,这是否是系统实现的方式并没有定义,也就是说,没有任何东西说它是有效的。
当调用虚函数时,类型系统开始查看与对象相关联的类型信息记录。当在NULL指针上调用虚函数时,不存在这样的信息,尝试访问它可能会导致某种崩溃。尽管如此,它并不需要,但它对大多数系统都是如此。
顺便说一句,main()always返回int

ryevplcw

ryevplcw2#

在C++中,类的方法不存储在该类的示例中。它们只是一些“特殊”函数,除了程序员指定的参数外,它们还透明地接受this指针。
在本例中,sayHi()方法不引用任何类字段,因此,this指针(即NULL)永远不会被跟踪。
但请不要搞错,这仍然是未定义的行为。当你调用这个函数时,你的程序可能会选择向你的联系人列表发送恶意的电子邮件。在这个特殊的例子中,它做了最糟糕的事情,而且似乎起作用了。
自从我回答了这个问题之后,virtual方法的情况就被添加了进来,但是我不会细化我的答案,因为它已经被其他人的答案包含了。

7eumitmz

7eumitmz3#

通常,从类示例化的对象的布局如下:

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

v_ptr是指向v-table的指针,其中包含对象的虚拟函数和RTTI数据的地址。没有虚函数的类没有v-table,相应的对象没有v_ptr
在上面的例子中,class A没有虚方法,因此没有v-table。这意味着要调用的sayHi()的实现可以在编译时确定,并且是不变的。
编译器生成的代码将隐式this指针设置为a,然后跳转到sayHi()的开头。由于实现不需要对象的内容,所以当指针为NULL时它可以工作是一个令人高兴的巧合。
如果你要使sayHi()成为虚拟的,编译器无法在编译时确定要调用的实现,所以生成的代码在v表中查找函数的地址并调用它。在aNULL的示例中,编译器读取地址0的内容,导致中止。

uxhixvfz

uxhixvfz4#

如果你调用一个类的非虚方法,对于编译器来说,知道这个函数属于哪个类就足够了,并且通过解引用(尽管是NULL)指向一个类的指针来调用这个方法,编译器就可以得到这个信息。**sayHi()**方法几乎只是一个函数,它将指向类示例的指针作为隐藏参数。这个指针是NULL,但是如果你没有在方法中引用任何属性,这也没关系。
一旦你把这个方法设为虚拟的,情况就改变了。编译器在编译时不再知道什么代码与方法相关联,必须在运行时弄清楚。它所做的是查看一个基本上包含所有虚方法的函数指针的表;该表与类示例相关联,因此它会查看与NULL指针相关的内存片段,因此在这种情况下会崩溃。

相关问题