通过NULL类指针调用类方法

时间:2010-03-24 04:24:41

标签: c++

我有以下代码段:

class ABC{
public:
        int a;
        void print(){cout<<"hello"<<endl;}
};

int main(){
        ABC *ptr = NULL:
        ptr->print();
        return 0;
}

它成功运行。有人可以解释一下吗?

10 个答案:

答案 0 :(得分:31)

使用不指向有效对象的指针调用成员函数会导致未定义的行为。什么事情都可能发生。它可以运行;它可能会崩溃。

在这种情况下,出现,因为this指针(未指向有效对象)未在print中使用。

答案 1 :(得分:14)

大多数答案都表示未定义的行为可能包括“出现”工作,而且他们是正确的。

亚历山大·马拉霍夫的回答给出了一些常见的实施细节,并解释了为什么你的情况似乎有效,但他做了一个轻微的错误陈述。他写道“因为你不使用这个arg,所以执行时这是正常的”,但是意思是“因为你不使用这个arg而执行时似乎没有问题”。

但要注意,您的代码仍然是未定义的行为。它打印了您想要的内容,并将您的银行账户余额转移到我的账户。谢谢你。

(SO风格说这应该是评论,但它太长了。虽然我做了CW。)

答案 2 :(得分:13)

(我不记得我从哪里得到这些知识,所以我可能完全错了)

在幕后,大多数编译器会将你的课程变成这样的事情:

struct _ABC_data{  
    int a ;  
};  
// table of member functions 
void _abc_print( _ABC_data* this );  

其中_ABC_data是C-style struct

,您的来电ptr->print();将转换为:

_abc_print( NULL)

执行时没问题,因为你没有使用this arg。


更新:(感谢Windows程序员right comment
这样的代码只适用于执行它的CPU 绝对没有理由利用这个实现功能。这就是为什么:

  1. 因为标准状态会产生未定义的行为(任何人都可以提供链接或至少参考(第N章,参数M ......)?)
  2. 如果您确实需要能够在没有实例的情况下调用成员函数,则使用 static 关键字为您提供所有可移植性和编译时检查

答案 3 :(得分:7)

导致未定义的行为。我用bit of work来解释原因。 :)但这是一个更技术性的答案。

基本上,未定义的行为意味着你不再保证程序的执行; C ++根本无话可说。它可以完全按照你想要的方式工作,或者它可能会惨不忍睹,或者它可以随机进行。

所以出现工作是未定义行为的完美结果,这就是你所看到的。实际的原因是,在您的实现(以及老实说,每个实现)中,this指针(被调用的实例的地址)在您的函数中根本没有被使用。也就是说,如果您尝试使用this指针(例如通过访问成员变量),您可能会崩溃。

请记住,上面的段落是特定于您的实现的,它是当前的行为。这只是一个猜测,你不能依赖它。

答案 4 :(得分:7)

根据C ++标准(5.2.5 / 3),表达式ptr->print();将隐式转换为(*ptr).print();。并且取消引用空指针会导致未定义的行为。幸运的是,有问题的代码在您的情况下可以正常运行。你不应该依赖它。

  

5.2.5 / 3:

     

如果E1具有“指向类的指针”类型   X,“那么表达式E1-> E2是   转换为等效形式   (*(E1))E2。 5.2.5的其余部分   将只解决第一个选项   (点)59)。缩写   objectexpression。 id-expression为   E1.E2,然后是类型和左值   这个表达式的属性是   确定如下。在里面   5.2.5的余数,cq代表   const或const不存在;   vq代表volatile或者   没有挥发性。 cv代表一个   任意一组cv限定符,如   在3.9.3中定义。

答案 5 :(得分:5)

虽然我不确定这是否是确切答案,但这是我的理解。 (另外,我的CPP术语很糟糕 - 如果可能的话,请忽略它)

对于C ++,当声明任何类(即尚未创建即时)时,函数将放在正在创建的二进制文件的.text部分中。创建瞬间时,函数或方法重复。也就是说,当编译器解析CPP文件时,它将用{.1}中定义的适当地址替换ptr->print()的函数调用。

因此,所有编译器都会根据函数ptr的{​​{1}}的类型替换适当的地址。 (这也意味着一些检查相关的公共/私人/继承等)

我为您的代码(名为print)执行了以下操作:

编辑:添加一些评论下面ASM(我真的是在ASM _not_好,我只能勉强读它 - 只要有足够的了解一些基本的东西) - 最好将读取this Wikibook link,其中我也做过:D 如果有人在ASW中发现错误,请发表评论 - 我很乐意修复它们并了解更多信息。

test12.cpp

v $ g++ test.cpp -S $ cat test.s ... // Following snippet is part of main function call movl $0, -8(%ebp) //this is for creating the NULL pointer ABC* ptr=NULL //It sets first 8 bytes on stack to '0' movl -8(%ebp), %eax //Load the ptr pointer into eax register movl %eax, (%esp) //Push the ptr on stack for using in function being called below //This is being done assuming that these elements would be used //in the print() function being called call _ZN3ABC5printE //Call to print function after pushing arguments (which are none) and //accesss pointer (ptr) on stack. ... 表示ZN3ABC5printEv中定义的函数的全局定义:

class ABC

因此,即使 ... .LC0: //This declares a label named .LC0 .string "hello" // String "hello" which was passed in print() .section .text._ZN3ABC5printEv,"axG",@progbits,_ZN3ABC5printEv,comdat .align 2 .weak _ZN3ABC5printEv //Not sure, but something to do with name mangling .type _ZN3ABC5printEv, @function _ZN3ABC5printEv: //Label for function print() with mangled name //following is the function definition for print() function .LFB1401: //One more lavbel pushl %ebp //Save the 'last' known working frame pointer .LCFI9: movl %esp, %ebp //Set frame (base pointer ebp) to current stack top (esp) .LCFI10: subl $8, %esp //Allocating 8 bytes space on stack .LCFI11: movl $.LC0, 4(%esp) //Pushing the string represented by label .LC0 in //in first 4 bytes of stack movl $_ZSt4cout, (%esp) //Something to do with "cout<<" statement call _ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc movl $_ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_, 4(%esp) movl %eax, (%esp) call _ZNSolsEPFRSoS_E //Probably call to some run time library for 'cout' leave //end of print() function ret //returning control back to main() ... 也能很好地运作。

答案 6 :(得分:4)

可能它运行是因为你的类指针没有在print函数中使用任何成员变量...如果在print函数中你试图访问它就不会运行...因为未初始化的类指针不能有初始化的成员变量。 ..

答案 7 :(得分:3)

正如其他人所说,这是未定义的行为。关于它似乎工作的原因是你没有尝试访问a内的成员变量print()。该类的所有实例共享print()代码的相同内存,因此访问该方法不需要this指针。但是,如果您尝试在方法中访问a,则最有可能获得访问冲突异常。

答案 8 :(得分:3)

这适用于我曾经尝试过的每个编译器(我已经尝试过很多)。是的,它是“未定义的”,但是当您调用非虚拟成员时,您不会取消引用指针。您甚至可以使用此“功能”编写代码,尽管纯粹主义者会对您大喊大叫,并称您为讨厌的名字等。

编辑:这里似乎有一些关于调用成员函数的混淆。当您调用非虚拟成员时,您不会取消引用“this”指针。您只是使用花哨的语法将其作为参数传递。这是我见过的所有实现,但不能保证。如果没有以这种方式实现,您的代码将运行得更慢。成员函数只是一个带有半隐藏参数的函数。而已!故事结局。话虽如此,可能有一些编译器由Cletus的slack jaw软件公司编写,但是我有一个问题,但我还没有碰到它。

答案 9 :(得分:1)

如果我使用简单的单词,这个会解释你。尝试用你想要的任何编译器编译它:)但请注意,它是根据标准的UB!

#include <iostream>
using namespace std;

class Armor
{
public:
    void set(int data)
    {
        cout << "set("<<data<<")\n";
        if(!this)
        {
            cout << "I am called on NULL object! I prefer to not crash!\n";
            return;
        }
        this->data = data;  //dereference it here
    }
    void get()
    {
        if(this) cout << "data = " << data << "\n";
        else cout << "Trying to dereference null pointer detected!\n";
    }
    int data;
};

int main()
{
   cout << "Hello World" << endl; 
   Armor a;
   a.set(100);
   a.get();


   Armor* ptr1 = &a;
   Armor* ptr2 = 0;

   ptr1->set(111);
   ptr2->set(222);

   ptr1->get();
   ptr2->get();

   return 0;
}

然后阅读__thiscall - 以及上述所有评论。

Hello World
set(100)
data = 100
set(111)
set(222)
I am called on NULL object! I prefer to not crash!
data = 111
Trying to dereference null pointer detected!