C ++标准(引自草案n3242)说明以下关于子对象[intro.object]:
除非对象是位字段或基类子对象为零 size,该对象的地址是它的第一个字节的地址 占据。两个不同的对象,既不是位字段也不是基础 零大小的类子对象应具有不同的地址。
现在,给出以下代码段:
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正在采用标准来表示如果一个子对象是零大小,你可以做任何你想做的事。
这是未指明的行为吗?
答案 0 :(得分:5)
扩展我之前的评论:
对象由其地址标识。如果比较两个相同类型对象的地址(如指针),并且它们比较相等,那么指针就要指向同一个对象。
不能以这种方式直接比较不同类型的对象,因此允许它们具有相同的地址。一个例子是结构及其第一个成员。它们不能是同一类型。基类和派生类都不能,因此如果基类为空,它们可能具有相同的地址。
但是,基类和派生类的第一个成员可以属于同一类型。除非基类也为空并且编译器尝试空基类优化,否则这不是问题。在这种情况下,我们可以指向相同类型的两个不同对象比较相等,因此相信它们是相同的对象。
因此,如果成员具有不同的类型(空和char),则它们可以具有相同的地址。如果它们属于同一类型,则不能,因为这会破坏对象标识的测试,如if (this != &that)
,有时用于测试自我分配等事情。
顺便说一句,微软同意这是他们的编译器中的一个错误,但还有其他更紧急的事情需要先解决。
答案 1 :(得分:4)
这与实现有关。
标准明确允许空基优化,但不需要它。事实上,该标准并不需要关于内存中类的布局的任何内容,只是某些类将彼此布局兼容(但不是共同布局)。还指定了成员顺序(当没有中间可访问性说明符时),但允许填充,页眉,页脚和各种更奇怪的东西。
答案 2 :(得分:3)
在C ++ 11标准的最终版本中,该段修改为:
除非对象是位字段或基类子对象为零 size,该对象的地址是它的第一个字节的地址 占据。两个不是位字段的对象可以具有相同的 如果一个是另一个的子对象或者如果至少一个是a,则寻址 零大小的基类子对象,它们是不同类型的; 否则,他们应有不同的地址。
虽然我不确定我是否理解这与物体的大小有什么关系。
答案 3 :(得分: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
的原因。