我今天去参加一个工作面试,被问了一个有趣的问题。
除了内存泄漏和没有虚拟dtor之外,为什么这段代码会崩溃?
#include <iostream>
//besides the obvious mem leak, why does this code crash?
class Shape
{
public:
virtual void draw() const = 0;
};
class Circle : public Shape
{
public:
virtual void draw() const { }
int radius;
};
class Rectangle : public Shape
{
public:
virtual void draw() const { }
int height;
int width;
};
int main()
{
Shape * shapes = new Rectangle[10];
for (int i = 0; i < 10; ++i)
shapes[i].draw();
}
4条答案
按热度按时间jaxagkaj1#
你不能这样索引。您已经分配了一个
Rectangles
数组,并在shapes
中存储了指向第一个数组的指针。当你执行shapes[1]
时,你是在解引用(shapes + 1)
。这不会给予你一个指向下一个Rectangle
的指针,而是一个指向Shape
数组中下一个Shape
的指针。当然,这是未定义的行为。就你而言,你很幸运,撞到了车。使用指向
Rectangle
的指针可以使索引正确工作。如果你想在数组中有不同种类的
Shape
s并多态地使用它们,你需要一个指向Shape的 * 指针 * 数组。oaxa6hgo2#
正如Martinho Fernandes所说,索引是错误的。如果你想存储一个Shape数组,你必须使用一个Shape * 的数组,如下所示:
请注意,您必须执行初始化Rectangle的额外步骤,因为初始化数组只设置指针,而不是对象本身。
omqzjyyz3#
当索引一个指针时,编译器将根据数组中的大小添加适当的数量。假设sizeof(Shape)= 4(因为它没有成员变量)。但是sizeof(Rectangle)= 12(确切的数字可能是错误的)。
所以当你索引开始于... 0x0的第一个元素,那么当你试图访问第10个元素,你试图去一个无效的地址或一个位置,这不是开始的对象。
soat7uwm4#
CB Bailey的This answer从语言规范的Angular 来看更准确。它更清楚地解释了为什么访问
shapes[i]
是未定义的行为。这个答案在下面引用,指针变量改为shapes
,Shape
作为基类,Rectangle
作为派生类(而不是那个问题中的p
,Base
和Derived
)。如果你看表达式
shapes[1]
,shapes
是一个Shape*(Shape
>一个完全定义的类型),1是一个int,所以根据ISO/IEC 14882:2003 5.2.1 [expr.sub],这个表达式是有效的,与*((shapes)+(1))
相同。因此,访问
shapes[i]
不是病态的。然而,如下所述,它是未定义的。从5.7 [expr.add] / 5开始,当一个整数加到一个指针上时,只有当指针指向数组对象的一个元素,并且指针运算的结果也指向该数组对象的一个元素或超过数组末尾的一个元素时,结果才是明确定义的。但是,
shapes
并不指向数组对象的元素,而是指向派生对象的基类子对象。数组成员是Rectangle
对象,而不是Shape
子对象。注意,在5.7 / 4中,出于加法运算符的目的,
Shape
子对象可以被视为大小为1的数组,因此从技术上讲,您可以形成地址shapes + 1
,但作为“最后一个元素”的指针,它并不指向Shape
对象,并且尝试读取或写入它将导致未定义的行为。这些段落很好地说明了如何处理表达式
shapes[i]
。