可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
This question already has an answer here:
-
What will happen when I call a member function on a NULL object pointer?
6 answers
I have following code snippet:
class ABC{
public:
int a;
void print(){cout<<\"hello\"<<endl;}
};
int main(){
ABC *ptr = NULL:
ptr->print();
return 0;
}
It runs successfully. Can someone explain it?
回答1:
(I can\'t recall where I\'ve got this knowledge, so I could be completely wrong)
Under the hood most compilers will transform your class to somthing like this:
struct _ABC_data{
int a ;
};
// table of member functions
void _abc_print( _ABC_data* this );
where _ABC_data is a C-style struct
and your call ptr->print();
will transform to:
_abc_print( NULL)
which is alright while execution since you do not use this
arg.
UPDATE: (Thanks to Windows programmer for right comment)
Such code is alright only for CPU which executes it.
There is absolutely positively no sane reason to exploit this implementation feature. And here is why:
- Because standard states it yields undefined behavior (could anyone give a link or at least reference(chapter N, par M...)?)
- If you actually need ability to call member function without instance, using static keyword gives you that with all the portability and compile-time checks
回答2:
Calling member functions using a pointer that does not point to a valid object results in undefined behavior. Anything could happen. It could run; it could crash.
In this case, it appears to work because the this
pointer, which does not point to a valid object, is not used in print
.
回答3:
Most answers said that undefined behaviour can include \"appearing\" to work, and they are right.
Alexander Malakhov\'s answer gave implementation details which are kind of common and explain why your situation appeared to work, but he made a slight misstatement. He wrote \"which is alright while execution since you do not use this arg\" but meant \"which appeared to be alright while execution since you do not use this arg\".
But be warned, your code still is undefined behaviour. It printed what you wanted AND it transfered the balance of your bank account to mine. I thank you.
(SO style says this should be a comment but it\'s too long. I made it CW though.)
回答4:
It leads to undefined behavior. I put a bit of work into explaining why. :) But that\'s a more technical answer.
Basically, undefined behavior means you are no longer guaranteed anything about the execution of the program; C++ simply has nothing to say. It could work exactly how you want, or it could crash miserably, or it could do both randomly.
So appearing to work is a perfectly fine result of undefined behavior, which is what you\'re seeing. The practical reason why is, on your implementation (and in honestly, every implementation), the this
pointer (the address of the instance being invoked) isn\'t being used at all in your function. That said, if you tried to use the this
pointer (for example by accessing a member variable), you\'d likely crash.
Remember, the above paragraph is something specific to your implementation and it\'s current behavior. It\'s just a guess and something you can\'t rely on.
回答5:
Expression ptr->print();
will be implicitly converted to (*ptr).print();
according to C++ Standard (5.2.5/3). And dereferencing the null pointer leads to undefined behaviour. It is fortuitous that the code in question works without errors in your case. You should not rely on it.
5.2.5/3:
If E1 has the type “pointer to class
X,” then the expression E1->E2 is
converted to the equivalent form
(*(E1)).E2; the remainder of 5.2.5
will address only the first option
(dot)59). Abbreviating
objectexpression. id-expression as
E1.E2, then the type and lvalue
properties of this expression are
determined as follows. In the
remainder of 5.2.5, cq represents
either const or the absence of const;
vq represents either volatile or the
absence of volatile. cv represents an
arbitrary set of cv-qualifiers, as
defined in 3.9.3.
回答6:
Though I am not sure if this is the exact answer, this is my understanding. (Also, my terminology for CPP is bad - ignore that if possible)
For C++, when any class is declared (i.e. no instant created yet), the functions are placed in the .text section of the binary being created. When an instant is created, Functions or Methods are not duplicated. That is, when the compiler is parsing the CPP file, it would replace the function calls for ptr->print()
with appropriate address defined in the .text section.
Thus, all the compiler would have done is replace appropriate address based on the type of ptr
for function print
. (Which also means some checking related public/private/inheritance etc)
I did the following for your code (named test12.cpp
):
EDIT: Adding some comments to ASM below ( I really am _not_ good at ASM, I can barely read it - just enough to understand some basic stuff) - best would be to read this Wikibook link, which I too have done :D
In case someone finds errors in the ASW, please do leave a comment - I would glad to fix them and learn more too.
$ 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.
...
vWhere ZN3ABC5printEv
represents the global definition of the function defined in 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()
...
Thus, even ((ABC *)0)->print();
works perfectly well.
回答7:
probably it runs because your class pointer is not using any member variable in print function...If in print function you try to access a it will not run... as uninitialized class pointer can\'t have initialized member variable...
回答8:
As others said, it is undefined behavior. Regarding the reason why it appears to work is that you are not trying to access the member variable a
inside the print()
. All the instances of the class share same memory for the code of print()
hence this
pointer is not required to access the method. However, if you try to access a
inside the method you are most likely to get an access violation exception.
回答9:
This works on every compiler I have ever tried it on (And I\'ve tried it on many). Yes it is \"undefined\" but you are not dereferencing the pointer when you call a non-virtual member. You can even write code using this \"feature\" although purists will yell at you and call you nasty names and such.
Edit: There seems to be some confusion here about calling member functions. You are NOT dereferencing the \'this\' pointer when you call a non-virtual member. You are simply using fancy syntax to pass it in as a parameter. This is with all implementations I have seen, but it\'s not guaranteed. If it wasn\'t implemented this way, your code would run slower. A member function is simply a function with and extra semi-hidden parameter. That\'s it! end of story. That being said there may be some compiler written by Cletus\' slack jaw software Co. that has a problem with this, but I haven\'t run into it yet.
回答10:
This one will explain you more than if I use plain words. Try compiling it with any compiler you want :) But note, it\'s UB according to standards!
#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;
}
Then read about __thiscall - and all comments above.
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!