令人惊讶的是,人们可能会将其称为功能,但我会说它是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
我们已经在不同的编译器中检查了这一点。平台但结果是相同的,但是如果我们通过没有对象的指针调用虚函数,那么就会出现运行时错误。这里的原因很明显,当检查对象时,虚函数找不到,因此出现错误。
答案 0 :(得分:3)
这不是错误。您触发了未定义的行为。您可能得到任何结果,包括您期望的结果。
取消引用NULL
指针是未定义的行为。
答案 1 :(得分:1)
正如所指出的,这是未定义的行为,所以任何事情都会发生。
要回答实施方面的问题,为什么会看到这种行为?
非虚拟调用仅作为普通函数调用实现,this
指针(值null
)作为参数传入。该参数未被解除引用(因为没有使用成员变量),因此调用成功。
虚拟呼叫需要在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
不能为空。