继question今天和与之相似主题的问题,群众早一点问,我在这里问这个问题从stadard的观点。
struct Base
{
int member;
};
struct Derived : Base
{
int another_member;
};
int main()
{
Base* p = new Derived[10]; // (1)
p[1].member = 42; // (2)
delete[] p; // (3)
}
根据标准是公形成-(1)
,因为Dervied*
(这是新表达的结果)可被隐式转换为Base*
( C ++ 11 draft,§4.10/ 3):
由于§5.3.5/ 3:,“指向 cv D的指针”类型的prvalue,其中D是类类型,可以是 转换为“指向 cv B的指针”类型的prvalue,其中B是基数 D类(第10条)如果B是无法进入的(第11条)或 模糊(10.2)D的基类,一个需要这个的程序 转换是不正确的。转换的结果是指向 派生类对象的基类子对象。空指针 value将转换为目标类型的空指针值。
(3)
会导致未定义的行为
在第一个备选方案(删除对象)中,如果是静态类型的话 要删除的对象不同于其动态类型,静态 type应该是对象的动态类型的基类 删除和静态类型应具有虚拟析构函数或 行为未定义。在第二个备选方案(删除数组)中 要删除的对象的动态类型与其静态类型不同, 行为未定义。
(2)
是否符合标准是合法的,还是会导致格式错误的程序或未定义的行为?
编辑:更好的措辞
答案 0 :(得分:4)
(3)导致未定义的行为,但严格来说并不是格式错误。 Ill-formed 意味着C ++程序不是根据语法规则,可诊断的语义规则和一个定义规则构建的。
同样(2),它的结构良好,但它并没有达到你所预期的效果。根据§8.3.4/ 6:
除了为类(13.5.5)声明它之外,下标运算符[]以这种方式解释 E1 [E2]与*((E1)+(E2))相同。由于适用于+的转换规则,如果E1是一个 数组和E2为整数,则E1 [E2]指E1的第E2个成员。因此,尽管它不对称 外观,下标是一种可交换的操作。
因此,在(2)中,当您可能希望获取地址p+sizeof(Base)*1
时,您将获得p+sizeof(Derived)*1
的结果地址。
答案 1 :(得分:4)
如果您查看表达式p[1]
,p
是Base*
(Base
是完全定义的类型),1
是{{} 1}},所以根据ISO / IEC 14882:2003 5.2.1 [expr.sub]这个表达式是有效的,并且与int
相同。
从5.7 [expr.add] / 5开始,当一个整数被添加到一个指针时,只有当指针指向一个数组对象的元素并且指针算术的结果也指向一个整数时才能很好地定义结果。该数组对象的元素或超过数组末尾的元素。但是,*((p)+(1))
不指向数组对象的元素,它指向p
对象的基类子对象。 Derived
对象是数组成员,而不是Derived
子对象。
请注意,在5.7 / 4下,出于加法运算符的目的,Base
子对象可以被视为大小为1的数组,因此从技术上讲,您可以形成地址Base
,但作为“一个过去的最后一个元素”指针,它不指向p + 1
对象,并且尝试读取或写入它将导致未定义的行为。
答案 2 :(得分:1)
标准不允许(2),但它仍然是危险的。
问题是,执行p[1]
意味着将sizeof(Base)
添加到基地址p
,并将该内存位置的数据用作Base
的实例。但是sizeof(Base)
小于sizeof(Derived)
的可能性非常高,因此您将在Derived
对象的中间解释一块内存,作为Base
对象
C++ FAQ Lite 21.4中的更多信息。
答案 3 :(得分:0)
p[1].member = 42;
状态良好。 p
的静态类型为Derived
,动态类型为Base
。 p[1]
等同于*(p+1)
,它似乎是有效的,并且是指向数组中动态类型Base
的第一个元素的指针。
但是,*(p+1)
实际上是指Derived
类型的数组成员。代码p[1].member = 42;
表示您认为您指的是类型为Base
的数组成员。