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!