【问题标题】:Will sizeof always be the same?sizeof 总是一样吗?
【发布时间】:2019-12-13 15:40:41
【问题描述】:

我有一个关于类结构、填充以及由此产生的sizeof 类的快速问题。在下面的示例中,在我测试过的每个编译器上,sizeof A 的结果总是 40 字节,这对我来说很有意义。

#include <iostream>
class A {
    int a, b; // 4 + 4
    short c;  // + 2
    double f; // not currently 8 byte aligned. Currently 10 bytes, so pad 6 extra bytes, so: + 6 + 8
    char w;   // + 1
    bool d;   // + 1
    double g; // not currently 8 byte aligned. Currently 26 bytes, so pad 6 extra bytes, so: + 6 + 8

    // 4 + 4 + 2 + 6 + 8 + 1 + 1 + 6 + 8 = 40
};

int main() {
    std::cout << sizeof( A );
}

我的问题是,这永远是真的吗? (假设每个成员的 alignof 和 size 不变)。我知道我可以对其重新排序,以便 doubles 排在第一位,然后缩小到 32 个字节;但是编译器可以做出这个决定吗?或者它总是程序员的责任? (我假设编译器无法重新排序)

【问题讨论】:

  • @KamilCuk,因此我还说如果每个成员的 sizeof 没有改变;)
  • 在你的机器上总是如此。如果你在另一个或不同的编译器上编译它,它可能会有所不同。
  • 如果你使用结构体,你可以强制禁用填充,因此结构体将始终保持相同的大小,而不管成员stackoverflow.com/a/8933773/3175478的顺序如何
  • @John:只有当你的编译器支持时。
  • 基本类型可以有多种大小,请参阅:en.cppreference.com/w/cpp/language/types

标签: c++ sizeof


【解决方案1】:

sizeof(A)在你的程序执行中永远是一样的。如果您更改成员的顺序或 A 的打包和重新编译方式,那么您可以/将获得不同的值。如果您使用不同的编译器进行编译,则大小也会发生变化。它也可以在使用相同编译器的不同机器上发生变化,因为不同机器的基本类型可以有不同的大小。

长话短说,如果你依赖于你的班级是一个特定的大小,那么添加一个static_assert 来检查它。这样,如果它确实发生了变化,你会得到一个很好的编译器错误,而不是可能的 UB。

我知道我可以重新排序它,这样双精度就会出现,它会缩小到 32 个字节;但是编译器可以做出这个决定吗?

不,编译器无法对成员重新排序以便更好地打包,因为您的类是标准布局类,并且所有成员必须按声明的顺序排列在内存中。

【讨论】:

  • 很好奇,与同一台机器上的另一个编译器相比,如果它不能重新排序,为什么编译器会做一些事情来改变大小?我目前有几个课程有static_assert( sizeof( class ) == 240 ) ),但不确定是否有必要。
  • @ChrisMM 他们可能会出于某种原因决定需要不同的填充。我怀疑如果基本尺寸都没有改变,但这是可能的。
【解决方案2】:

编译器不允许重新排序成员,因为它们都具有相同的访问级别。因此,例如,如果有一个 public 成员,那么编译器可以重新排序您的成员变量。

参考:https://timsong-cpp.github.io/cppwp/class.mem#19

C++ 标准不保证类的大小,除非它必须保证成员可以单独寻址。

【讨论】:

  • C++ 是否允许编译器重新排序成员? (我知道这可能发生在 D 中,但我曾认为 C++ 按声明顺序将它们放在内存中。)
  • @Eljay:这不是我第一次在如此基本的事情上犯错,但这是我从小就想到的。如果我不正确,请投反对票。
  • 当你在一个类中混合访问级别时,排序可能会改变:timsong-cpp.github.io/cppwp/class.mem#19
【解决方案3】:

sizeof 总是一样吗?

sizeof 是编译时间常数。因此对于任何已编译的程序,sizeof 在执行之间不会改变。但是,对于程序的不同编译没有这样的保证。事实上,当为另一个系统编译程序时,即使是基本类型的 sizeof 和 alignof 也很典型。

我知道我可以重新排序...编译器可以做出这个决定吗?

标准布局类有一些限制。第一个成员保证与超级对象具有相同的地址。此外,对于在声明顺序开始时具有相同类型成员的任何此类类,可以通过单独的联合成员访问“公共初始序列”,这实际上意味着它们必须具有相同的地址,这实际上是防止对成员进行任何重新排序,因为编译器无法知道可能存在哪些其他具有潜在共享公共初始序列的类。 A 是一个标准的布局类。

这些限制不适用于非标准布局类,允许编译器更加宽松。据我所知,该语言不会阻止编译器选择非标准布局类成员的顺序。但实际上,为了将单独编译的目标文件链接在一起,这些目标文件必须符合相同的二进制接口,即它们需要具有相同的类型表示。如果编译器在一次编译中使用一种布局,而在另一种编译中使用不同的布局,则会破坏 ABI。因此,无论编译器选择什么顺序,它都应该坚持下去。任何打算兼容的编译器都应该使用相同的 ABI。对于 GCC 使用的 Itanium ABI 的价值,它指定:

2.4 非 POD 类类型

二。虚拟基地以外的成员分配

对于每个数据组件 D(首先是 C 的主要基类,如果有的话,然后是声明顺序的非主要、非虚拟直接基类,然后是 非静态数据成员和未命名的位域按声明顺序)...


或者它总是程序员的责任? (我假设编译器无法重新排序)

程序员最好假设子对象是按声明顺序排列的,并以最小化填充的方式选择该顺序。最好不要为了访问非标准布局类的内存而假定子对象的顺序。

【讨论】:

    【解决方案4】:

    不允许编译器对类中的属性进行重新排序(在构造和解构成员时使用此顺序,因此可能会改变程序的含义)。

    但是,您也不能指望 sizeof 总是相同的结果。如果出现一个 64 位 int 比 32 位更有意义的架构怎么办?在这种情况下,尺寸会更大。

    【讨论】:

      猜你喜欢
      • 2010-11-09
      • 2013-09-19
      • 1970-01-01
      • 2019-10-18
      • 2012-08-03
      • 2013-10-20
      • 2019-02-12
      • 1970-01-01
      相关资源
      最近更新 更多