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
可以使用参数化构造函数进行初始化。- 来自 PrintVal(( 的
*ptr
值应为 10。
对于 1( 我越来越invalid conversion from 'Sample*' to 'int' [-fpermissive]
.我正确地调用了构造函数,为什么会这样? 对于 2( 我得到垃圾值或程序得到分段错误的情况。这不应该发生,因为应该删除 SomeFunc 的本地对象x
的ptr
,而不是s1
的ptr
,因为它是通过值而不是引用传递的。IIRC 按对象值传递将对象的副本发送到函数的接收参数。
您的代码确实具有未定义的行为。但让我们从头开始。
Sample s1 = new Sample(10);
这是这一行中发生的情况:
在- 堆上分配一个
Sample
对象,new
表达式返回指向它的指针,即Sample*
。 - 不能将
Sample*
分配给类型为Sample
的变量。但是Sample
有一个构造函数,它允许从 int 隐式构造。如果您使用-fpermissive
编译器选项(提示:不要!(,编译器允许将指针隐式转换为整数 - 毕竟,指针只是一个内存地址,也称为数字。 - 因此,
s1
通过将堆Sample
对象的内存地址解释为整数(如果sizeof(Sample*) > sizeof(int)
则截断它(来构造的。这就是最终成为*(s1.ptr)
的值。
重申关键点:在该行中,您不实例化一个Sample
对象,而是实例化两个。错误 1:在堆上创建的永远不会被删除。这是内存泄漏。
SomeFunc(s1);
Sample
中没有任何内容阻止编译器生成默认复制构造函数和默认复制赋值运算符。重要提示:指针的"默认"表示复制指针,而不是其后面的对象。所以:
s1
被复制到调用SomeFunc()
。该副本在函数中可作为x
使用。由于默认指针,s1
和x
都指向同一个int
对象。x
函数结束时超出范围,析构函数将运行并删除int
对象。
我们还没有完全确定,但我们正在接近。
s1.PrintVal();
该函数尝试访问指针后面的int
对象,但它已被删除。s1.ptr
是一个悬空的指针。错误 2:取消引用悬空指针是未定义的行为。
而这一切都是因为那个看似无辜的隐式指针到 int 转换......这就是为什么默认情况下它是一个编译器错误,至少在非古代编译器中是这样。