指向派生对象数组的基指针

发布于 2024-12-01 14:45:27 字数 1068 浏览 1 评论 0原文

继今天早些时候在这里提出的问题以及大量类似主题的问题之后,我'我来这里是想从斯塔德的角度来问这个问题。

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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

执手闯天涯 2024-12-08 14:45:28
p[1].member = 42; 

形状良好。 p 的静态类型是 Derived,动态类型是 Basep[1] 相当于 *(p+1) ,它似乎是有效的,并且是指向动态类型 Base 的第一个元素的指针大批。

然而,*(p+1) 实际上指的是 Derived 类型的数组成员。代码 p[1].member = 42; 显示您认为您正在引用类型为 Base 的数组成员。

p[1].member = 42; 

is well formed. Static type for p is Derived and dynamic type is Base. p[1] is equivalent to *(p+1) which seems a valid and is a pointer to first element of dynamic type Base in array.

However, *(p+1) in fact refers to an array member of type Derived. Code p[1].member = 42; shows you think you are referring to an array member with type Base.

箹锭⒈辈孓 2024-12-08 14:45:27

如果你看一下表达式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 a Base* (Base is a completely-defined type) and 1 is an int, 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 a Derived object. It is the Derived object that is an array member, not the Base 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 address p + 1, but as a "one past the last element" pointer, it doesn't point at a Base object and attempting to read from or write to it will cause undefined behavior.

你げ笑在眉眼 2024-12-08 14:45:27

(3) 导致未定义的行为,但严格来说它并不是格式错误的。 格式错误是指 C++ 程序不是按照语法规则、可诊断语义规则和单一定义规则构建的。

与 (2) 相同,它的格式良好,但它没有达到您可能预期的效果。根据§8.3.4/6:

除了为类声明的地方 (13.5.5) 之外,下标运算符 [] 的解释方式如下
E1[E2] 与 *((E1)+(E2)) 相同。由于适用于 + 的转换规则,如果 E1 是
数组,E2 为整数,则 E1[E2] 指 E1 的第 E2 个成员。因此,尽管其不对称
外观上,下标是一种交换运算。

因此,在 (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:

Except where it has been declared for a class (13.5.5), the subscript operator [] is interpreted in such a way
that E1[E2] is identical to *((E1)+(E2)). Because of the conversion rules that apply to +, if E1 is an
array and E2 an integer, then E1[E2] refers to the E2-th member of E1. Therefore, despite its asymmetric
appearance, subscripting is a commutative operation.

So in (2) you will get the address which is the result of p+sizeof(Base)*1 when you probably wanted to get the address p+sizeof(Derived)*1.

捶死心动 2024-12-08 14:45:27

该标准并没有禁止(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 adding sizeof(Base) to the base address p, and using the data at that memory location as an instance of Base. But chances are very high that sizeof(Base) is smaller than sizeof(Derived), so you'll be interpreting a block of memory starting in the middle of a Derived object, as a Base object.

More information in C++ FAQ Lite 21.4.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文