通过NULL类指针调用类方法

Adi*_*dil 38 c++

我有以下代码片段:

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

int main(){
        ABC *ptr = NULL:
        ptr->print();
        return 0;
}
Run Code Online (Sandbox Code Playgroud)

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

Jam*_*lis 31

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

在这种情况下,它似乎工作,因为this没有使用指向有效对象的指针print.


Ale*_*hov 14

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

在引擎盖下,大多数编译器会将你的类转换成这样的东西:

struct _ABC_data{  
    int a ;  
};  
// table of member functions 
void _abc_print( _ABC_data* this );  
Run Code Online (Sandbox Code Playgroud)

其中_ABC_data是C风格的结构

并且您的电话ptr->print();将转换为:

_abc_print( NULL)
Run Code Online (Sandbox Code Playgroud)

因为你不使用thisarg,所以在执行时没问题.


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

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

  • @wds:为什么要通过vtable调用非虚函数? (2认同)

Win*_*mer 14

大多数答案说未定义的行为可能包括"出现"工作,他们是对的.

Alexander Malakhov的回答给出了一些常见的实施细节,并解释了为什么你的情况似乎有效,但他做了一个轻微的错误陈述.他写道"因为你不使用这个arg,所以执行时没什么问题"但是意思是"因为你不使用这个arg而执行时似乎没什么问题".

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

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

  • 为什么要这是一个评论?我认为这是一个很好的答案. (2认同)

GMa*_*ckG 7

它导致未定义的行为.我做了一些工作来解释原因.:)但这是一个更技术性的答案.

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

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

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


Kir*_*sky 7

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

5.2.5/3:

如果E1具有"指向类X的指针"类型,则表达式E1-> E2被转换为等效形式(*(E1)).E2; 5.2.5的剩余部分将仅解决第一个选项(点)59).缩写客观表达.id-expression为E1.E2,然后此表达式的type和lvalue属性确定如下.在5.2.5的其余部分中,cq表示const或不存在const; vq代表挥发性或不存在挥发性.cv表示一组任意的cv限定符,如3.9.3中所定义.


Shr*_*rey 5

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

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

因此,所有的编译器可能做的就是代替基于适当的地址类型ptr的功能print.(这也意味着一些检查相关的公共/私人/继承等)

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

编辑:在下面给ASM添加一些评论(我真的不擅长ASM,我几乎无法阅读它 - 只够了解一些基本内容) - 最好是阅读这个Wikibook链接,我也做过:D以防万一有人在ASW中发现错误,请发表评论 - 我很乐意修复它们并了解更多信息.

$ 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.
...
Run Code Online (Sandbox Code Playgroud)

v 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() 
  ...
Run Code Online (Sandbox Code Playgroud)

因此,即使((ABC *)0)->print();效果也很好.


归档时间:

查看次数:

8466 次

最近记录:

9 年,2 月 前