为不应该获得未定义行为的内容获取未定义的行为

  • 本文关键字:未定义 获取 不应该 c++
  • 更新时间 :
  • 英文 :

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();
}

我认为应该发生两件事:

  1. s1可以使用参数化构造函数进行初始化。
  2. 来自 PrintVal(( 的*ptr值应为 10。

对于 1( 我越来越invalid conversion from 'Sample*' to 'int' [-fpermissive].我正确地调用了构造函数,为什么会这样? 对于 2( 我得到垃圾值或程序得到分段错误的情况。这不应该发生,因为应该删除 SomeFunc 的本地对象xptr,而不是s1ptr,因为它是通过值而不是引用传递的。IIRC 按对象值传递将对象的副本发送到函数的接收参数。

您的代码确实具有未定义的行为。但让我们从头开始。

Sample s1 = new Sample(10);

这是这一行中发生的情况:

  1. 堆上分配一个Sample对象,new表达式返回指向它的指针,即Sample*
  2. 不能将Sample*分配给类型为Sample的变量。但是Sample有一个构造函数,它允许从 int 隐式构造。如果您使用-fpermissive编译器选项(提示:不要!(,编译器允许将指针隐式转换为整数 - 毕竟,指针只是一个内存地址,也称为数字。
  3. 因此,s1通过将堆Sample对象的内存地址解释为整数(如果sizeof(Sample*) > sizeof(int)则截断它(来构造的。这就是最终成为*(s1.ptr)的值。

重申关键点:在该行中,您不实例化一个Sample对象,而是实例化两个。错误 1:在堆上创建的永远不会被删除。这是内存泄漏。

SomeFunc(s1);

Sample中没有任何内容阻止编译器生成默认复制构造函数和默认复制赋值运算符。重要提示:指针的"默认"表示复制指针,而不是其后面的对象。所以:

  1. s1被复制到调用SomeFunc()。该副本在函数中可作为x使用。由于默认指针,s1x都指向同一个int对象。
  2. x函数结束时超出范围,析构函数将运行并删除int对象。

我们还没有完全确定,但我们正在接近。

s1.PrintVal();

该函数尝试访问指针后面的int对象,但它已被删除。s1.ptr是一个悬空的指针。错误 2:取消引用悬空指针是未定义的行为。

而这一切都是因为那个看似无辜的隐式指针到 int 转换......这就是为什么默认情况下它是一个编译器错误,至少在非古代编译器中是这样。

最新更新