c++标准(引自n3242草案)对子对象[intro.object]有如下规定:
除非对象是位域或基类的子对象为0大小,该对象的地址是它的第一个字节的地址占据了。两个不同的对象,既不是位域也不是基大小为0的类子对象必须有不同的地址。
现在,给定下面的代码片段:
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认为这个标准的意思是,如果一个子对象的大小为零,你就可以做任何你想做的事情。
这是未指定的行为吗?
扩展我之前的评论:
对象由其地址标识。如果比较两个相同类型对象的地址(如指针),如果比较结果相等,则认为这两个指针指向同一个对象。
不同类型的对象不能以这种方式直接比较,因此允许它们具有相同的地址。一个例子是结构体及其第一个成员。它们不能是同一类型的。基类和派生类也不能,所以如果基类为空,它们可能具有相同的地址。
但是,基类和派生类的第一个成员可以具有相同的类型。这不是问题,除非基类也是空的,并且编译器尝试空基类优化。在这种情况下,指向同一类型的两个不同对象的指针比较相等,因此可以认为它们是同一个对象。因此,如果成员具有不同的类型(empty和char),它们可以具有相同的地址。如果它们是相同的类型,它们不能,因为这会破坏对对象标识的测试,比如if (this != &that)
,有时用来测试像自我赋值这样的东西。
顺便说一下,微软同意这是他们编译器中的一个错误,但有其他更紧急的事情要先修复。
这取决于实现。
标准明确允许空基优化,但不要求它。事实上,该标准对内存中类的布局没有太多要求,只要求某些类彼此之间的布局兼容(但不要求通用的布局是什么)。还指定了成员的顺序(当没有中间的可访问性说明符时),但是允许填充、页眉、页脚和各种奇怪的东西。
在c++ 11标准的最终版本中,该段落被修改为:
除非对象是位域或基类的子对象为0大小,该对象的地址是它的第一个字节的地址占据了。两个不是位域的对象可以有相同的如果其中一个是另一个的子主语,或者至少有一个是大小为零的基类子对象,它们具有不同的类型;否则,它们必须有不同的地址。
虽然我不确定我理解这与对象的大小有什么关系
这篇文章有很好的解释。我只是想补充一下,为了解决这个结构膨胀的问题,你可以简单地使empty
类成为一个模板,这样用不同的模板参数实例化它就会使它成为一个不同的类:
template<class T>
struct empty { };
struct member: empty<member> { };
struct derived: empty<derived> { member m; };
int main(void)
{
printf("%dn", sizeof(derived));
return 0;
}
输出1
。
这就是在大型项目中避免使用boost::noncopyable
的原因。