我有以下代码段:
class ABC{
public:
int a;
void print(){cout<<"hello"<<endl;}
};
int main(){
ABC *ptr = NULL:
ptr->print();
return 0;
}
它成功运行。有人可以解释一下吗?
答案 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
绝对没有理由利用这个实现功能。这就是为什么:
答案 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!