0

可能重复:
通过 NULL 类指针调用类方法

面试时被问到这个问题,有人能回答吗?

#include<string>
#include<iostream>
#include <stdio.h>

using namespace std;

class A
{
int k;
public:
     void f1()
    {

     int i;
     printf("1");

    }

     void f2()
    {

     k = 3;
     printf("3");

    }

};
class B
{
int i;
public:
    virtual void f1()
    {
    printf("2");
    scanf("%d",&i);
    }

};


int main()
{
    A* a = NULL;
    B* b = NULL;

    a->f1(); // works why?(non polymorphic)
    b->f1(); // fails why?(polymorphic)
            a->f2(); //fails why?
}

最后两种情况属于多态类。第一种情况是普通类。我知道,如果我在 A 的 f1 中访问 i ,它将再次给出运行时异常。但我不明白为什么会这样

4

5 回答 5

2

我同意其他帖子的观点,即这是未定义的行为,这意味着在执行程序时可能发生任何事情,包括“做正确的事”。

现在,让我们看看调用是如何实现的:

a->f1()是一个普通的方法调用(非虚拟)。大多数编译器将以与以下代码类似的方式编译它:

    class A { int i; }
    void f1(A* a) { int i; printf("1"); }

这意味着 this 指针实际上像函数的参数一样被处理(实际上,经常有一些关于如何处理 this 指针的优化,但这在这里无关紧要)。现在,由于 f1 不使用 this 指针,因此它为 null 的事实不会导致崩溃。

a->f2()实际上会崩溃,因为它使用了 this 指针:它更新了this->k.

调用b->f1()是一个虚函数调用,这通常使用虚拟表查找来实现,如b->vtable[0](). 由于 b 为空,读取虚拟表的取消引用崩溃。

于 2012-10-08T15:06:55.540 回答
2
a->f1();
b->f1();
a->f2();

在所有这三种情况下,您都在引用指向 NULL 的指针,即它不指向对象。这构成了未定义的行为。它们可能纯属偶然,但你不能依赖它。试图弄清楚为什么一个版本可能工作也没有多大意义。未定义的行为意味着任何事情都可能发生。

于 2012-10-08T14:51:57.317 回答
1

从技术上讲,这都是未定义的行为。因此,如果没有更多背景(编译器,使用的设置),这将是正确的答案。

我不相信这是他们期望听到的。

鉴于通常以这种方式在内部翻译成员函数调用(故意简化):

class A {
    void foo(int x) {}  // compiler creates function void A_foo(A* this, int x) {}
};

A a;
a.foo(5); // compiler calls A_foo(&a, 5);

但是虚拟功能的情况有所不同。这里不解释虚拟调度的原理,简单起见——最终调用的函数取决于对象的动态类型。如果对象不存在,程序就无法知道调用什么函数。

至于你a->f2()失败的原因。想象一下这个功能A_f2(A* this)。在您内部访问 A 的成员k。这将在我的简化编译器中被翻译成this->k = 3. 但在实际调用this中是一个空指针。

于 2012-10-08T14:59:49.833 回答
0

所有三个示例都会导致未定义的行为,因此这是非常特定于实现的,并且不能保证在所有编译器上都具有相同的行为。

实现虚函数的一种常见方法是在类的开头添加指向函数指针表的指针。每当调用虚函数时,程序都会跟随 this 指针并在表中查找以确定调用哪个函数。由于在空指针的示例中,它正在查看该指针的无效地址,这会导致运行时错误。

调用非虚函数时,编译器已经知道要调用什么函数,所以可以直接插入对该函数的调用;不必访问对象来确定调用哪个函数。因此,如果函数本身不访问对象,则函数调用将永远不会导致通过空指针进行访问,因此不会导致运行时错误。

于 2012-10-08T15:06:09.343 回答
0

在某种程度上,这三种情况都没有“起作用”。但另一方面,这三种情况都“有效”。

它们都有未定义的行为,因为它们都通过空指针执行间接操作。

我知道如果我在 A 的 f1 中访问 i 它会再次给出运行时异常

也许,也许不是。未定义的行为是未定义的,所以任何事情都可能发生。

于 2012-10-08T14:50:55.210 回答