C++ C+中的PODs和继承+;11结构的地址是否=第一个成员的地址?

C++ C+中的PODs和继承+;11结构的地址是否=第一个成员的地址?,c++,c++11,language-lawyer,C++,C++11,Language Lawyer,(我编辑这个问题是为了避免分心。有一个核心问题需要澄清,然后其他问题才有意义。向那些现在答案似乎不太相关的人道歉。) 让我们举一个具体的例子: struct Base { int i; }; 没有虚拟方法,也没有继承,通常是一个非常愚蠢和简单的对象。因此,它是一个可预测的布局。特别是: Base b; &b == reinterpret_cast<B*>&(b.i); 同样,没有虚拟方法,没有虚拟继承,也没有多重继承。所以这也是POD 问题:这个事实(C+

(我编辑这个问题是为了避免分心。有一个核心问题需要澄清,然后其他问题才有意义。向那些现在答案似乎不太相关的人道歉。)

让我们举一个具体的例子:

struct Base {
    int i;
};
没有虚拟方法,也没有继承,通常是一个非常愚蠢和简单的对象。因此,它是一个可预测的布局。特别是:

Base b;
&b == reinterpret_cast<B*>&(b.i);
同样,没有虚拟方法,没有虚拟继承,也没有多重继承。所以这也是POD

问题:这个事实(C++11中派生的是POD)是否允许我们说:

Derived d;
&d == reinterpret_cast<D*>&(d.i); // true on g++-4.6

事先没有填充?

你不在乎吊舱的尺寸,你在乎的是标准的布局。以下是标准第9节
[class]
中的定义:

标准布局类是指:

  • 没有非标准布局类(或此类类型的数组)或引用类型的非静态数据成员
  • 没有虚拟函数(10.3)和虚拟基类(10.1)
  • 对所有非静态数据成员具有相同的访问控制(第11条)
  • 没有非标准布局基类

  • 在最派生的类中没有非静态数据成员,并且最多有一个基类具有非静态数据成员,或者没有基类具有非静态数据成员,以及
  • 没有与第一个非静态数据成员类型相同的基类
然后,您想要的属性将得到保证(第9.2节
[class.mem]
):

指向标准布局结构对象的指针(使用
重新解释\u cast
进行适当转换)指向其初始成员(或者如果该成员是位字段,则指向其所在的单元),反之亦然

这实际上比旧的需求要好,因为通过添加非平凡的构造函数和/或析构函数,不会丢失
重新解释\u cast
的能力


现在让我们转到第二个问题。答案不是你所希望的

Base *b = new Derived;
delete b;
是未定义的行为,除非
Base
具有虚拟析构函数。见第5.3.5节(
[expr.delete]

在第一个备选方案(删除对象)中,如果要删除的对象的静态类型与其动态类型不同,则静态类型应为要删除的对象的动态类型的基类,并且静态类型应具有虚拟析构函数,或者行为未定义


您先前使用
malloc
free
的代码片段基本上是正确的。这将有助于:

Base *b = new (malloc(sizeof(Derived))) Derived;
free(b);

由于指针
b
的值与placement new返回的地址相同,而placement new返回的地址又与
malloc
返回的地址相同,因此对上述无关问题进行了大量讨论。是的,主要是为了C兼容性,只要您知道自己在做什么,就有许多保证可以信赖。然而,所有这些都与你的主要问题无关。主要问题是:是否存在这样的情况,即可以使用与对象的动态类型不匹配的指针类型删除对象,并且指向的类型没有虚拟析构函数。答案是:没有


这种逻辑可以从运行时系统应该做的事情中派生出来:它获取一个指向对象的指针,并被要求删除它。如果要定义派生类析构函数,它需要存储有关如何调用派生类析构函数或对象实际占用的内存量的信息。然而,这意味着在使用内存方面可能会有相当大的成本。例如,如果第一个成员需要非常严格的对齐,例如在8字节边界处对齐,就像在
double
中一样,添加大小将增加至少8字节的开销以分配内存。尽管这听起来可能不太糟糕,但这可能意味着只有一个对象(而不是两个或四个)适合缓存线,从而大大降低了性能。

您的最后一段代码可能会说:

Base *b = new Derived;
delete b;  // delete b, not d.
在这种情况下,简单的答案是它仍然是未定义的行为。问题中的类或结构是POD、标准布局或可复制的事实并没有真正改变任何事情

是的,您正在传递正确的地址,是的,您和我都知道,在本例中,dtor几乎是一个nop——尽管如此,您传递到
delete
的指针的静态类型与动态类型不同,并且静态类型没有虚拟dtor。标准很清楚,这会给出未定义的行为

从实际的角度来看,如果你真的坚持,你很可能不用UB——很有可能你所做的不会有任何有害的副作用,至少对于大多数典型的编译器来说是这样。然而,请注意,即使是最好的代码也是非常脆弱的,因此看似微不足道的更改可能会破坏一切——甚至切换到具有非常繁重的类型检查的编译器,这样也可以做到这一点


就你的论点而言,情况很简单:这基本上意味着,如果委员会愿意,他们可能会做出这种明确的行为。然而,据我所知,它从未被提出过,即使它被提出过,它也可能是一个非常低优先级的项目——它并没有增加太多内容,也没有启用新的编程风格,等等。

这是对的补充,而不是替代

你可能会认为这只是一个技术问题。将其称为“未定义”的标准只是一点语义上的胡扯,除了允许编译器编写者无缘无故地做一些愚蠢的事情之外,没有任何现实效果。但事实并非如此

我可以看到理想的实现,其中:

Base *b = new Derived;
delete b;
导致了非常奇怪的行为。这是因为
Base *b = new (malloc(sizeof(Derived))) Derived;
free(b);
Base *b = new Derived;
delete b;  // delete b, not d.
Base *b = new Derived;
delete b;
struct Base {
};

struct Derived {
   int an_int;
};