C++ 通过空类指针调用类方法

C++ 通过空类指针调用类方法,c++,C++,我有以下代码片段: class ABC{ public: int a; void print(){cout<<"hello"<<endl;} }; int main(){ ABC *ptr = NULL: ptr->print(); return 0; } ABC类{ 公众: INTA; void print(){cout它会导致未定义的行为。我在解释原因时加了一个注释。:)但这是一

我有以下代码片段:

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

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

void print(){cout它会导致未定义的行为。我在解释原因时加了一个注释。:)但这是一个更专业的答案

基本上,未定义的行为意味着你不再保证程序的执行;C++简单地说什么,它可以精确地工作,或者它可能会崩溃,或者它可以随机地进行。 因此,看起来有效是未定义行为的完美结果,这就是您所看到的。实际原因是,在您的实现中(老实说,在每个实现中,
this
指针(被调用实例的地址)在您的函数中根本没有使用。也就是说,如果您试图使用
this
指针(例如通过访问成员变量),您可能会崩溃


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

使用不指向有效对象的指针调用成员函数会导致未定义的行为。任何情况都可能发生。它可能会运行,也可能会崩溃


在这种情况下,它似乎起作用是因为
这个
指针没有指向有效的对象,在
打印
中没有使用。它运行的原因可能是您的类指针没有在打印函数中使用任何成员变量……如果在打印函数中尝试访问某个对象,它将不会运行……因为未初始化的类指针无法运行已初始化成员变量…

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

(我记不起从何处获得此知识,因此我可能完全错了)

在这种情况下,大多数编译器会将您的类转换为以下内容:

struct _ABC_data{  
    int a ;  
};  
// table of member functions 
void _abc_print( _ABC_data* this );  
其中_ABC_数据是一个

您的调用
ptr->print();
将转换为:

_abc_print( NULL)
这在执行时是正常的,因为您不使用
this
arg


更新:(感谢Windows程序员的帮助)
这样的代码只适用于执行它的CPU。
绝对没有合理的理由利用此实现功能。原因如下:


  • 因为标准表示它产生未定义的行为(任何人都可以给出链接或至少引用(第n章,PAM))
  • 如果您确实需要在没有实例的情况下调用成员函数,那么使用static关键字可以提供所有可移植性和编译时检查

  • 大多数回答说,未定义的行为可能包括“出现”工作,他们是对的

    Alexander Malakhov的回答给出了一些常见的实现细节,并解释了为什么您的情况似乎有效,但他做了一个轻微的错误陈述。他写道“在执行过程中,因为您不使用此参数,这是正常的”,但意思是“在执行过程中,因为您不使用此参数,这似乎是正常的”

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


    (风格是说这是注释,但太长了。我把它做成了CW)。< P>表达式>代码> PTR>打印();< /C> >隐式转换为<代码>(*PTR).Primter();< /C> >根据C++标准(5.2.5/ 3)。并且取消对空指针的引用会导致未定义的行为。在您的情况下,有问题的代码能够正常工作是很偶然的。您不应该依赖它

    5.2.5/3:

    如果E1具有类型“指向类的指针 十、 那么表达式E1->E2是 转换为等效形式 (*(E1)).E2;5.2.5的剩余部分 将只处理第一个选项 (点号)59.缩写 objectexpression.id表达式为 E1.E2,然后是类型和左值 此表达式的属性为 确定如下。在 5.2.5的剩余部分,cq代表 const或不存在const; vq表示volatile或 不存在volatile.cv表示 任意一组cv限定符,如 定义见3.9.3


    虽然我不确定这是否是准确的答案,但这是我的理解。(此外,我对CPP的术语不好——如果可能,忽略这一点)

    对于C++,当声明任何类(即没有立即创建)时,函数被放置在创建的二进制文本的.text部分中。当创建一个瞬间时,函数或方法不会被复制。也就是说,当编译器解析CPP文件时,它将替换函数调用:
    在.text部分中定义了适当的地址

    因此,编译器所要做的就是根据函数
    print
    ptr
    类型替换相应的地址(这也意味着检查相关的公共/私有/继承等)

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

    编辑:在下面的ASM中添加一些评论(我真的不擅长ASM,我几乎看不懂它-只够理解一些基本的东西)-最好是阅读,我也做过:D 如果有人在ASW中发现错误,请留下评论-我很高兴修复它们并了解更多信息

    v此处
    ZN3ABC5printEv
    表示在
    类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() 
      ...
    
    。。。
    .LC0://这声明了一个名为.LC0的标签
    .string“hello”//string“hello”whic
    
      ...
      .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() 
      ...
    
    #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;
    }
    
    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!