指向派生对象数组的基指针
继今天早些时候在这里提出的问题以及大量类似主题的问题之后,我'我来这里是想从斯塔德的角度来问这个问题。
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*
(这是 new-expression 的结果)可以隐式转换为Base*
(C++11 草案,§4.10/3):
“指向cv D”类型的纯右值,其中 D 是类类型,可以是 转换为“指向cv B”类型的纯右值,其中 B 是基数 D 的类别(第 10 条)。如果 B 是不可访问的(第 11 条)或 D 的不明确 (10.2) 基类,需要此的程序 转换格式不正确。转换的结果是一个指向 派生类对象的基类子对象。空指针 value 被转换为目标类型的空指针值。
由于 §5.3.5/3,(3)
会导致未定义的行为:
在第一个替代方案(删除对象)中,如果 要删除的对象与其动态类型不同,静态类型 类型应是要创建的对象的动态类型的基类 已删除且静态类型应具有虚拟析构函数或 行为未定义。在第二种选择(删除数组)中,如果 要删除的对象的动态类型与其静态类型不同, 行为未定义。
根据标准,(2)
是合法的还是会导致格式错误的程序或未定义的行为?
编辑:更好的措辞
Following a question asked here earlier today and multitudes of similary themed questions, I'm here to ask about this problem from stadard's viewpoint.
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)
}
According to standard (1)
is well-formed, because Dervied*
(which is the result of new-expression) can be implicitly converted to Base*
(C++11 draft, §4.10/3):
A prvalue of type “pointer to cv D”, where D is a class type, can be
converted to a prvalue of type “pointer to cv B”, where B is a base
class (Clause 10) of D. If B is an inaccessible (Clause 11) or
ambiguous (10.2) base class of D, a program that necessitates this
conversion is ill-formed. The result of the conversion is a pointer to
the base class subobject of the derived class object. The null pointer
value is converted to the null pointer value of the destination type.
(3)
leads to undefined behaviour because of §5.3.5/3:
In the first alternative (delete object), if the static type of the
object to be deleted is different from its dynamic type, the static
type shall be a base class of the dynamic type of the object to be
deleted and the static type shall have a virtual destructor or the
behavior is undefined. In the second alternative (delete array) if the
dynamic type of the object to be deleted differs from its static type,
the behavior is undefined.
Is (2)
legal according to standard or does it lead to ill-formed program or undefined behaviour?
edit: Better wording
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
形状良好。
p
的静态类型是Derived
,动态类型是Base
。p[1]
相当于*(p+1)
,它似乎是有效的,并且是指向动态类型Base
的第一个元素的指针大批。然而,
*(p+1)
实际上指的是Derived
类型的数组成员。代码p[1].member = 42;
显示您认为您正在引用类型为Base
的数组成员。is well formed. Static type for
p
isDerived
and dynamic type isBase
.p[1]
is equivalent to*(p+1)
which seems a valid and is a pointer to first element of dynamic typeBase
in array.However,
*(p+1)
in fact refers to an array member of typeDerived
. Codep[1].member = 42;
shows you think you are referring to an array member with typeBase
.如果你看一下表达式
p[1]
,p
是一个Base*
(Base
是一个完全-定义的类型)并且1
是一个int
,因此根据 ISO/IEC 14882:2003 5.2.1 [expr.sub] 该表达式是有效的并且与*((p)+(1))
。从5.7 [expr.add] / 5开始,当一个整数与一个指针相加时,只有当指针指向数组对象的一个元素并且指针算术的结果也指向该数组对象的一个元素时,结果才是明确定义的数组对象或数组末尾的一个对象。然而,
p
并不指向数组对象的元素,它指向Derived
对象的基类子对象。Derived
对象是数组成员,而不是Base
子对象。请注意,在 5.7 / 4 下,出于加法运算符的目的,
Base
子对象可以被视为大小为 1 的数组,因此从技术上讲,您可以形成地址p + 1
,但作为“最后一个元素之后的一个”指针,它并不指向Base
对象,并且尝试读取或写入它会导致未定义的行为< /em>。If you look at the expression
p[1]
,p
is aBase*
(Base
is a completely-defined type) and1
is anint
, so according to ISO/IEC 14882:2003 5.2.1 [expr.sub] this expression is valid and identical to*((p)+(1))
.From 5.7 [expr.add] / 5, when an integer is added to a pointer, the result is only well defined when the pointer points to an element of an array object and the result of the pointer arithmetic also points the an element of that array object or one past the end of the array.
p
, however, does not point to an element of an array object, it points at the base class sub-object of aDerived
object. It is theDerived
object that is an array member, not theBase
sub-object.Note that under 5.7 / 4, for the purposes of the addition operator, the
Base
sub-object can be treated as an array of size one, so technically you can form the addressp + 1
, but as a "one past the last element" pointer, it doesn't point at aBase
object and attempting to read from or write to it will cause undefined behavior.(3) 导致未定义的行为,但严格来说它并不是格式错误的。 格式错误是指 C++ 程序不是按照语法规则、可诊断语义规则和单一定义规则构建的。
与 (2) 相同,它的格式良好,但它没有达到您可能预期的效果。根据§8.3.4/6:
因此,在 (2) 中,当您可能想要获取地址
p+sizeof(Derived)*1< 时,您将获得作为
p+sizeof(Base)*1
结果的地址/代码>。(3) leads to undefined behaviour, but it is not ill-formed strictly speaking. Ill-formed means that a C++ program is not constructed according to the syntax rules, diagnosable semantic rules, and the One Definition Rule.
Same for (2), it is well-formed, but it does not do what you have probably expected. According to §8.3.4/6:
So in (2) you will get the address which is the result of
p+sizeof(Base)*1
when you probably wanted to get the addressp+sizeof(Derived)*1
.该标准并没有禁止(2),但它仍然很危险。
问题是,执行
p[1]
意味着将sizeof(Base)
添加到基地址p
,并使用该内存位置的数据作为Base
的实例。但是sizeof(Base)
小于sizeof(Derived)
的可能性非常高,因此您将解释从中间开始的内存块>派生
对象,作为基
对象。更多信息请参见C++ FAQ Lite 21.4。
The standard doesn't disallow (2), but it's dangerous nevertheless.
The problem is that doing
p[1]
means addingsizeof(Base)
to the base addressp
, and using the data at that memory location as an instance ofBase
. But chances are very high thatsizeof(Base)
is smaller thansizeof(Derived)
, so you'll be interpreting a block of memory starting in the middle of aDerived
object, as aBase
object.More information in C++ FAQ Lite 21.4.