C ++指针可以在没有Object的情况下调用Member Function

时间:2016-06-13 08:54:05

标签: c++ pointers instantiation member-functions

令人惊讶的是,人们可能会将其称为功能,但我会说它是C ++的另一个错误,我们可以通过指针调用成员函数而不分配任何对象。请参阅以下示例:

class A{    
public:   
       virtual void f1(){cout<<"f1\n";}
       void f2(){cout<<"f2\n";};
};

int main(){    
    A *p=0;
    p->f2();
    return 0;
}

输出: F2

我们已经在不同的编译器中检查了这一点。平台但结果是相同的,但是如果我们通过没有对象的指针调用虚函数,那么就会出现运行时错误。这里的原因很明显,当检查对象时,虚函数找不到,因此出现错误。

5 个答案:

答案 0 :(得分:3)

这不是错误。您触发了未定义的行为。您可能得到任何结果,包括您期望的结果。

取消引用NULL指针是未定义的行为。

BTW,没有诸如“C ++的bug”之类的东西。这些错误可能出现在C ++编译器中,而不是它自己的语言。

答案 1 :(得分:1)

正如所指出的,这是未定义的行为,所以任何事情都会发生。

要回答实施方面的问题,为什么会看到这种行为?

  1. 非虚拟调用仅作为普通函数调用实现,this指针(值null)作为参数传入。该参数未被解除引用(因为没有使用成员变量),因此调用成功。

  2. 虚拟呼叫需要在vtable中查找以获取要呼叫的实际功能的地址。 vtable地址存储在对象本身数据的指针中。因此,为了阅读它,需要对this指针进行去引用 - 分段错误。

答案 2 :(得分:0)

创建课程时
class A{    
public:   
       virtual void f1(){cout<<"f1\n";}
       void f2(){cout<<"f2\n";};
};

编译器将成员函数的代码放在文本区域中。 当您执行p->MemberFunction()时,编译器只需对p进行推理,并尝试使用MemberFunction的{​​{1}}类型信息查找函数p

现在因为函数的代码存在于文本区域中所以它被调用。如果该函数引用了某些类变量,那么在访问它们时,由于没有对象,您可能得到Class A,但由于情况并非如此,因此函数可以正常执行。

注意:这完全取决于编译器如何实现成员函数访问。某些编译器可能会在访问成员函数之前选择查看对象的指针是否为null,但是指针可能有一些垃圾值而不是编译器无法检查的Segmentation Fault,因此通常编译器会忽略此检查。

答案 3 :(得分:0)

你可以通过未定义的行为实现很多目标。你甚至可以调用一个只接受1个参数的函数,并接收第二个参数:

#include <iostream>

void Func(int x)
{
    uintptr_t ptr = reinterpret_cast<uintptr_t>(&x) + sizeof(x);
    uintptr_t* sPtr = (uintptr_t*)ptr;
    const char* secondArgument = (const char*)*sPtr;
    std::cout << secondArgument << std::endl;
}

int main()
{
    typedef void(*PROCADDR)(int, const char*);
    PROCADDR ext_addr = reinterpret_cast<PROCADDR>(&Func);

    //call the function
    ext_addr(10, "arg");
    return 0;
}

在Windows下编译并运行,您将获得&#34; arg&#34;作为第二个参数的结果。这不是C ++中的错误,对我来说这简直是愚蠢的:)

答案 4 :(得分:0)

这适用于大多数编译器。当您调用方法(非虚拟)时,编译器将转换:

public async Task CallerAsync()
{
    int[] numbers = new int[] { 1, 2, 3 };
    Dictionary<int, string> dictionary = await ConvertToDictionaryAsync(numbers);
}

public async Task<Dictionary<int, string>> ConvertToDictionaryAsync(int[] numbers)
{
    var dict = new Dictionary<int, string>();

    for (int i = 0; i < numbers.Length; i++)
    {
        var n = numbers[i];
        dict[n] = await DoSomethingReturnString(n);
    }

    return dict;
}

到某事:

obj.foo();

其中foo(&obj); 成为&obj方法的this指针。使用指针时:

foo

对于编译器来说,它只不过是:

Obj *pObj = NULL;
pObj->foo();

即:

foo(pObj);

使用空指针调用任何函数都不是犯罪,空指针(即具有空值的指针)将被推送到调用堆栈。由目标函数决定是否将null传递给它。这就像打电话:

foo(NULL);

哪个会编译,运行,如果处理它:

strlen(NULL);

因此,这非常有效:

 size_t strlen(const char* ptr) {

     if (ptr==NULL) return 0;
     ...  // rest of code if `ptr` is not null
}

只要((A*)NULL)->f2(); 是非虚拟的,并且f2不会读取/写入f2之外的任何内容,包括任何虚函数调用。静态数据和功能访问仍然可以。

然而,如果方法是虚拟的,函数调用并不像它看起来那么简单。编译器会添加一些额外的代码来执行给定函数的后期绑定。后期绑定完全基于this指针所指向的内容。它取决于编译器,但调用如:

this

将涉及通过虚函数表查找查找当前类型的obj->virtual_fun(); 。因此,obj不能为空。