class Sample
{
public:
int *ptr;
Sample(int i)
{
ptr = new int(i);
}
~Sample()
{
delete ptr;
}
void PrintVal()
{
cout << "The value is " << *ptr;
}
};
void SomeFunc(Sample x)
{
cout << "Say i am in someFunc " << endl;
}
int main()
{
Sample s1 = new Sample(10);
SomeFunc(s1);
s1.PrintVal();
}
我认为应该发生的两件事:
s1
可以使用参数化构造函数进行初始化。*ptr
的值应为10。对于1)我得到invalid conversion from 'Sample*' to 'int' [-fpermissive]
。我正确地调用了构造函数,为什么会这样呢?
对于2)的情况,我得到的是垃圾值,或者程序遇到分段错误。这不会发生,因为应该删除SomeFunc的本地对象ptr
的{{1}}而不是x
的{{1}},因为它是通过值而不是引用传递的。 IIRC通过对象传递值将对象的副本发送到函数的接收参数。
答案 0 :(得分:1)
您的代码确实具有未定义的行为。但是,让我们从头开始。
Sample s1 = new Sample(10);
这是此行中发生的事情:
Sample
对象,并且new
表达式返回指向它的指针Sample*
。Sample*
分配给类型Sample
的变量。但是Sample
具有允许从int隐式构造的构造函数。如果使用-fpermissive
编译器选项(提示:不要!),则编译器允许将指针隐式转换为整数-毕竟,指针只是一个内存地址,也就是一个数字。s1
通过将堆Sample
对象的内存地址解释为整数(如果sizeof(Sample*) > sizeof(int)
则将其截断)来构造。那就是最后以*(s1.ptr)
开头的值。重申关键点:在这一行中,您没有实例化一个Sample
对象,而是两个。 Bug 1 :永远不会删除在堆上创建的一个。那是内存泄漏。
SomeFunc(s1);
Sample
中没有任何内容阻止编译器生成默认的副本构造函数和默认的副本分配运算符。重要:指针的“默认”是指复制指针,而不是复制其后的对象。所以:
s1
被复制以调用SomeFunc()
。该副本在函数中以x
的形式提供。由于存在默认的指针复制,因此s1
和x
都指向同一个int
对象。x
在函数末尾超出范围,析构函数运行并删除int
对象。我们还没有确定相当,但是我们已经接近了。
s1.PrintVal();
该函数尝试访问指针后面的int
对象,但该对象已被删除。 s1.ptr
是一个悬空指针。 错误2 :取消引用悬空指针是未定义的行为。
所有这些都是由于看似无害的隐式指针到int转换……这就是为什么默认情况下它是编译器错误的原因,至少在非古代编译器中是如此。