繁体   English   中英

C ++指针可以在没有对象的情况下调用成员函数

[英]C++ Pointer can call Member Function without Object

令人惊讶的是,人们可能将其称为功能,但是我经常说它是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

我们已经在不同的编译器和平台上进行了检查,但是结果是相同的,但是,如果我们通过没有对象的指针调用虚拟函数,则会发生运行时错误。 在检查对象时,虚函数的原因很明显,因此找不到错误。

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

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

顺便说一句,没有诸如“ C ++的bug”之类的东西。 在C ++编译器中,错误可能不是以其自身的语言出现的。

如前所述,这是未定义的行为,因此一切都会发生。

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

  1. 非虚拟调用仅作为普通函数调用实现,并且this指针(值null )作为参数传入。 该参数未取消引用(因为未使用任何成员变量),因此调用成功。

  2. 虚拟调用需要在vtable进行查找,以获取要调用的实际函数的地址。 vtable地址存储在对象本身数据中的指针中。 因此要读取它,必须取消引用this指针-分段错误。

当您通过创建课程时

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

编译器将成员函数的代码放在文本区域中。 当您执行p->MemberFunction() ,编译器仅p->MemberFunction() p并尝试使用p的类型信息(即Class A查找函数MemberFunction

现在,由于函数的代码存在于文本区域中,因此将其调用。 如果函数具有对某些类变量的引用,则在访问它们时,您可能会遇到“ Segmentation Fault因为没有对象,但是由于并非如此,因此该函数可以正确执行。

注意 :这完全取决于编译器如何实现成员函数访问。 一些编译器可以选择在访问成员函数之前查看对象的指针是否为null,但是指针可能具有一些垃圾值而不是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下编译并运行,第二个参数的结果为“ arg”。 这不是C ++的错,对我而言只是愚蠢的:)

这将适用于大多数编译器。 调用方法(非虚拟方法)时,编译器会翻译:

obj.foo();

到某事:

foo(&obj);

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

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

对于编译器,它不过是:

foo(pObj);

即:

foo(NULL);

用空指针调用任何函数都不构成犯罪,空指针(即具有空值的指针)将被压入调用堆栈。 由目标函数检查是否将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的当前类型。 因此, obj不能为null。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM