5

C++ 标准(引自草案 n3242)对子对象 [intro.object] 有以下说明:

除非对象是位域或大小为零的基类子对象,否则该对象的地址就是它占用的第一个字节的地址。既不是位字段也不是零大小的基类子对象的两个不同对象应具有不同的地址。

现在,给定以下代码段:

struct empty { };
struct member: empty { };
struct derived: empty { member m; };

int main(void)
{
    printf("%d", sizeof(derived));
    return 0;
}

我相信 gcc 会打印出来2,Visual C++ 2010 会打印出来1。我怀疑 gcc 采用标准意味着如果类型的存储代表不同的对象,则不能为它们的存储设置别名。我敢打赌,MSVC 采用的标准意味着如果一个子对象的大小为零,你可以做任何你想做的事情。

这是未指定的行为吗?

4

4 回答 4

5

扩展我之前的评论:

对象由其地址标识。如果比较相同类型的两个对象的地址(如指针)并且它们比较相等,则认为指针指向同一个对象。

这种方式不能直接比较不同类型的对象,所以允许它们具有相同的地址。一个例子是一个结构和它的第一个成员。它们不能是同一类型。基类和派生类也不能,因此如果基类为空,它们可能具有相同的地址。

但是,基类和派生类的第一个成员可以是同一类型。这不是问题,除非基类也是空的并且编译器尝试空基类优化。在这种情况下,我们可以让指向相同类型的两个不同对象的指针比较相等,因此认为它们是同一个对象。

因此,如果成员具有不同的类型(空和字符),它们可以具有相同的地址。如果它们属于同一类型,则不能,因为这会破坏对对象身份的测试,例如if (this != &that),有时用于测试诸如自分配之类的事情。

顺便说一句,微软同意这是他们编译器中的一个错误,但还有其他更紧急的事情需要先修复。

于 2011-10-14T16:32:09.840 回答
4

这取决于实现。

该标准明确允许空基优化,但不需要它。事实上,该标准对内存中类的布局没有太多要求,只是某些类将相互布局兼容(但不是常见的布局)。还指定了成员的顺序(当没有中间的可访问性说明符时),但允许使用填充、页眉、页脚和各种奇怪的东西。

于 2011-10-13T20:45:47.667 回答
3

在 C++11 标准的最终版本中,该段被修改为:

除非对象是位域或大小为零的基类子对象,否则该对象的地址就是它占用的第一个字节的地址。如果一个对象是另一个对象的子对象,或者如果至少一个是大小为零的基类子对象并且它们属于不同类型,则两个不是位域的对象可能具有相同的地址;否则,它们应具有不同的地址。

虽然我不确定我是否理解这与对象的大小有什么关系。

于 2011-10-13T21:15:08.450 回答
1

在这个线程中有很好的解释。我只是想添加它来解决这个结构膨胀问题,您可以简单地将empty类设为模板,以便使用不同的模板参数对其进行实例化使其成为不同的类:

template<class T>
struct empty { };
struct member: empty<member> { };
struct derived: empty<derived> { member m; };

int main(void)
{
    printf("%d\n", sizeof(derived));
    return 0;
}

输出1

这是避免boost::noncopyable在大型项目中使用的原因。

于 2011-10-14T17:01:07.903 回答