[英]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 ++编译器中,错误可能不是以其自身的语言出现的。
如前所述,这是未定义的行为,因此一切都会发生。
要回答实施方面的问题,为什么会看到这种行为?
非虚拟调用仅作为普通函数调用实现,并且this
指针(值null
)作为参数传入。 该参数未取消引用(因为未使用任何成员变量),因此调用成功。
虚拟调用需要在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.