当前位置:  开发笔记 > 编程语言 > 正文

访问NULL指针上的类成员

如何解决《访问NULL指针上的类成员》经验,为你挑选了4个好方法。

我正在尝试使用C++,发现下面的代码非常奇怪.

class Foo{
public:
    virtual void say_virtual_hi(){
        std::cout << "Virtual Hi";
    }

    void say_hi()
    {
        std::cout << "Hi";
    }
};

int main(int argc, char** argv)
{
    Foo* foo = 0;
    foo->say_hi(); // works well
    foo->say_virtual_hi(); // will crash the app
    return 0;
}

我知道虚方法调用崩溃,因为它需要vtable查找,并且只能使用有效对象.

我有以下问题

    非虚方法如何处理say_hiNULL指针?

    对象在哪里foo分配?

有什么想法吗?



1> Rob Kennedy..:

该对象foo是一个带有类型的局部变量Foo*.该变量很可能在main函数的堆栈上分配,就像任何其他局部变量一样.但存储的foo是空指针.它并不指向任何地方.在Foo任何地方都没有任何类型的实例.

要调用虚函数,调用者需要知道调用函数的对象.那是因为对象本身就是告诉哪个函数应该被调用的原因.(这通常是通过给对象一个指向vtable的指针,一个函数指针列表来实现的,而调用者只知道它应该调用列表中的第一个函数,而不事先知道指针指向的位置.)

但是要调用非虚函数,调用者不需要知道所有这些.编译器确切地知道将调用哪个函数,因此它可以生成CALL机器代码指令以直接转到所需的函数.它只是将指向函数的对象的指针传递给函数的隐藏参数.换句话说,编译器将您的函数调用转换为:

void Foo_say_hi(Foo* this);

Foo_say_hi(foo);

现在,由于该函数的实现永远不会引用其this参数指向的对象的任何成员,因此您实际上避免了取消引用空指针的子弹,因为您从不取消引用它.

形式上,在空指针上调用任何函数(甚至是非虚函数)都是未定义的行为.未定义行为的允许结果之一是您的代码似乎完全按照您的意图运行.不应该依赖,虽然有时你会发现从你的编译器供应商库,依赖于这一点.但是编译器供应商的优势在于能够为未定义的行为添加进一步的定义.不要自己动手.


实际上,事后看来,它更直接来自于`[C++ 11:5.2.5/2]`:"表达式`E1-> E2`被转换为等价形式`(*(E1)).E2` "然后,当它不是一个有效的指针(包括`[C++ 11:3.8/2]`)时,显而易见的解除引用`E1`的UB.

2> Pontus Gagge..:

所述say_hi()成员函数通常是由编译器所实施

void say_hi(Foo *this);

由于您不访问任何成员,因此您的调用成功(即使您根据标准输入了未定义的行为).

Foo 根本没有分配.



3> 小智..:

取消引用NULL指针会导致"未定义的行为",这意味着任何事情都可能发生 - 您的代码甚至可能正常工作.但是,您不能依赖于此 - 如果您在不同的平台上运行相同的代码(甚至可能在同一平台上),它可能会崩溃.

在您的代码中没有Foo对象,只有一个指针,其值为NULL.



4> bayda..:

这是未定义的行为.但是,如果您不访问成员变量和虚拟表,大多数编译器都会生成正确处理此情况的指令.

让我们看看visual studio中的反汇编,了解会发生什么

   Foo* foo = 0;
004114BE  mov         dword ptr [foo],0 
    foo->say_hi(); // works well
004114C5  mov         ecx,dword ptr [foo] 
004114C8  call        Foo::say_hi (411091h) 
    foo->say_virtual_hi(); // will crash the app
004114CD  mov         eax,dword ptr [foo] 
004114D0  mov         edx,dword ptr [eax] 
004114D2  mov         esi,esp 
004114D4  mov         ecx,dword ptr [foo] 
004114D7  mov         eax,dword ptr [edx] 
004114D9  call        eax  

你可以看到Foo:say_hi被称为通常的功能,但在ecx寄存器中有这个.为简化起见,您可以假设是作为隐式参数传递的,我们从未在您的示例中使用过.
但在第二种情况下,我们计算虚拟表的功能地址 - 由于foo地址和获取核心.


在debuger下调试 - > Windows->反汇编
推荐阅读
勤奋的瞌睡猪_715
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有