当int64_t更改为int32_t时,为什么类大小会增加

xin*_*aiz 28 c++ memory-alignment memory-layout bit-fields object-layout

在我的第一个例子中,我使用了两个位域int64_t.当我编译并获得类的大小时,我得到8.

class Test
{
    int64_t first : 40;
    int64_t second : 24;
};

int main()
{
    std::cout << sizeof(Test); // 8
}
Run Code Online (Sandbox Code Playgroud)

但是,当我将第二个bitfeild更改int32_t为类的大小时,会增加到16:

class Test
{
    int64_t first : 40;
    int32_t second : 24;
};

int main()
{
    std::cout << sizeof(Test); // 16
}
Run Code Online (Sandbox Code Playgroud)

这种情况发生在GCC 5.3.0和MSVC 2015上.但为什么呢?

Nat*_*ica 37

在你的第一个例子中

int64_t first : 40;
int64_t second : 24;
Run Code Online (Sandbox Code Playgroud)

二者firstsecond用一个64位的整数的64位.这会导致类的大小为单个64位整数.在你的第二个例子中

int64_t first : 40;
int32_t second : 24;
Run Code Online (Sandbox Code Playgroud)

这是两个独立的位字段存储在两个不同的内存块中.您使用64位整数的40位,然后使用24位另一个32位整数.这意味着您至少需要12个字节(此示例使用8位字节).很可能你看到的额外4个字节是填充以在64位边界上对齐类.

正如其他答案和评论所指出的那样,这是实现定义的行为,您可以/将在不同的实现上看到不同的结果.

  • @KyleStrand我说的原因.编译器将其视为单独的位字段,因此它不会将它们组合在一起.另外,对于C语言,supercat的引用也是如此,这个问题被标记为C++.C++不继承整个C标准.我找不到C在C++标准中的相同要求. (4认同)
  • C++可能会或者可能没有从C中带来一个可能存在或者可能不存在的要求 - 我今天早上没有心情去挖掘标准 - 但是有一个要求,在ABI中有详细说明文档,至少在C++标准中是隐含的,对于POD结构(这是),其布局与C等效结果完全相同.如果不是这种情况,C和C++之间的函数调用互操作性将是不可能的(通常). (3认同)
  • 据我所知,这里唯一*相关的事实是supercat提到的C标准要求:"位域需要存储在*指定类型的对象内,或其签名/无符号等效物." 你的答案似乎暗示`int32_t`在某种程度上受到*alignment*约束的约束,该约束比强加于`int64_t`的约**更严格,这没有任何意义. (2认同)
  • 你说过第二个例子是"两个不同的字段存储在两个不同的内存块中",大概是通过"单独的块"你的意思是*包含对象*(基元类型)是分开的,而在原始的是只有一个`int64_t`原语表示内存的"块".(这很模糊,因为人们可能会认为原始的位域本身是40和24位,每个都是"单独的块".)但是你没有说明*为什么*位域不能在一个单独的"块"内存中表示.这是supercat解释的关键点. (2认同)

sup*_*cat 15

C标准的位域规则不够精确,无法告诉程序员任何有关布局的有用信息,但仍然拒绝实现本来可能有用的自由.

特别是,需要将位域存储具有所指示类型的对象或其签名/无符号等效物中.在第一个示例中,第一个位域必须存储在int64_t或uint64_t对象中,第二个位域同样存在,但是它们有足够的空间容纳在同一个对象中.在第二个示例中,第一个位域必须存储在int64_t或uint64_t中,第二个位域必须存储在int32_t或uint32_t中.即使在结构的末尾添加了额外的位字段,uint64_t也会有24位"搁浅"; uint32_t有8位,目前没有使用,但是可以使用另一个宽度小于8的int32_t或uint32_t位域添加到该类型中.

恕我直言,该标准抨击了给编译器自由与给程序员有用的信息/控制之间最糟糕的平衡,但它就是它的本质.我个人认为,如果首选语法允许程序员根据普通对象精确地指定其布局(例如,位域"foo"应存储在3位,从第4位(值16开始),字段"),那么位域将更有用. foo_bar")但我知道没有计划在标准中定义这样的东西.

  • C11:"实现可以分配足够大的任何可寻址存储单元以保持位字段.如果剩余足够的空间,则紧接在结构中的另一个位字段之后的位字段将被打包到同一单元的相邻位中.如果剩余的空间不足,那么是否将不适合的位域放入下一个单元或重叠相邻单元是否是实现定义的.我没有发现任何关于被强制存储在声明类型的对象中的内容. (6认同)

Jus*_*ica 6

添加其他人已经说过的内容:

如果要检查它,可以使用编译器选项或外部程序来输出结构布局.

考虑这个文件:

// test.cpp
#include <cstdint>

class Test_1 {
    int64_t first  : 40;
    int64_t second : 24;
};

class Test_2 {
    int64_t first  : 40;
    int32_t second : 24;
};

// Dummy instances to force Clang to output layout.
Test_1 t1;
Test_2 t2;
Run Code Online (Sandbox Code Playgroud)

如果我们使用布局输出标志,例如Visual Studio /d1reportSingleClassLayoutX(其中X是全部或部分类或结构名称)或Clang ++ -Xclang -fdump-record-layouts(其中-Xclang告诉编译器将其解释-fdump-record-layouts为Clang前端命令而不是GCC前端命令),我们可以转储的存储器布局Test_1Test_2标准输出.[不幸的是,我不确定如何直接与GCC这样做.]

如果我们这样做,编译器将输出以下布局:

  • 视觉工作室:
cl /c /d1reportSingleClassLayoutTest test.cpp

// Output:
tst.cpp
class Test_1    size(8):
    +---
 0. | first (bitstart=0,nbits=40)
 0. | second (bitstart=40,nbits=24)
    +---



class Test_2    size(16):
    +---
 0. | first (bitstart=0,nbits=40)
 8. | second (bitstart=0,nbits=24)
    | <alignment member> (size=4)
    +---
Run Code Online (Sandbox Code Playgroud)
  • 铛:
clang++ -c -std=c++11 -Xclang -fdump-record-layouts test.cpp

// Output:
*** Dumping AST Record Layout
   0 | class Test_1
   0 |   int64_t first
   5 |   int64_t second
     | [sizeof=8, dsize=8, align=8
     |  nvsize=8, nvalign=8]

*** Dumping IRgen Record Layout
Record: CXXRecordDecl 0x344dfa8 <source_file.cpp:3:1, line:6:1> line:3:7 referenced class Test_1 definition
|-CXXRecordDecl 0x344e0c0 <col:1, col:7> col:7 implicit class Test_1
|-FieldDecl 0x344e1a0 <line:4:2, col:19> col:10 first 'int64_t':'long'
| `-IntegerLiteral 0x344e170 <col:19> 'int' 40
|-FieldDecl 0x344e218 <line:5:2, col:19> col:10 second 'int64_t':'long'
| `-IntegerLiteral 0x344e1e8 <col:19> 'int' 24
|-CXXConstructorDecl 0x3490d88 <line:3:7> col:7 implicit used Test_1 'void (void) noexcept' inline
| `-CompoundStmt 0x34912b0 <col:7>
|-CXXConstructorDecl 0x3490ee8 <col:7> col:7 implicit constexpr Test_1 'void (const class Test_1 &)' inline noexcept-unevaluated 0x3490ee8
| `-ParmVarDecl 0x3491030 <col:7> col:7 'const class Test_1 &'
`-CXXConstructorDecl 0x34910c8 <col:7> col:7 implicit constexpr Test_1 'void (class Test_1 &&)' inline noexcept-unevaluated 0x34910c8
  `-ParmVarDecl 0x3491210 <col:7> col:7 'class Test_1 &&'

Layout: <CGRecordLayout
  LLVMType:%class.Test_1 = type { i64 }
  NonVirtualBaseLLVMType:%class.Test_1 = type { i64 }
  IsZeroInitializable:1
  BitFields:[
    <CGBitFieldInfo Offset:0 Size:40 IsSigned:1 StorageSize:64 StorageOffset:0>
    <CGBitFieldInfo Offset:40 Size:24 IsSigned:1 StorageSize:64 StorageOffset:0>
]>

*** Dumping AST Record Layout
   0 | class Test_2
   0 |   int64_t first
   5 |   int32_t second
     | [sizeof=8, dsize=8, align=8
     |  nvsize=8, nvalign=8]

*** Dumping IRgen Record Layout
Record: CXXRecordDecl 0x344e260 <source_file.cpp:8:1, line:11:1> line:8:7 referenced class Test_2 definition
|-CXXRecordDecl 0x344e370 <col:1, col:7> col:7 implicit class Test_2
|-FieldDecl 0x3490bd0 <line:9:2, col:19> col:10 first 'int64_t':'long'
| `-IntegerLiteral 0x344e400 <col:19> 'int' 40
|-FieldDecl 0x3490c70 <line:10:2, col:19> col:10 second 'int32_t':'int'
| `-IntegerLiteral 0x3490c40 <col:19> 'int' 24
|-CXXConstructorDecl 0x3491438 <line:8:7> col:7 implicit used Test_2 'void (void) noexcept' inline
| `-CompoundStmt 0x34918f8 <col:7>
|-CXXConstructorDecl 0x3491568 <col:7> col:7 implicit constexpr Test_2 'void (const class Test_2 &)' inline noexcept-unevaluated 0x3491568
| `-ParmVarDecl 0x34916b0 <col:7> col:7 'const class Test_2 &'
`-CXXConstructorDecl 0x3491748 <col:7> col:7 implicit constexpr Test_2 'void (class Test_2 &&)' inline noexcept-unevaluated 0x3491748
  `-ParmVarDecl 0x3491890 <col:7> col:7 'class Test_2 &&'

Layout: <CGRecordLayout
  LLVMType:%class.Test_2 = type { i64 }
  NonVirtualBaseLLVMType:%class.Test_2 = type { i64 }
  IsZeroInitializable:1
  BitFields:[
    <CGBitFieldInfo Offset:0 Size:40 IsSigned:1 StorageSize:64 StorageOffset:0>
    <CGBitFieldInfo Offset:40 Size:24 IsSigned:1 StorageSize:64 StorageOffset:0>
]>
Run Code Online (Sandbox Code Playgroud)

请注意,用于生成此输出的Clang I版本(Rextester使用的版本)似乎默认将两个位域优化为单个变量,并且我不确定如何禁用此行为.


lor*_*ond 5

标准说:

§9.6位域

类对象中位域的分配是实现定义的.位字段的对齐是实现定义的.[ 注意:位字段跨越某些机器上的分配单元而不是其他机器上的分配单元.在某些机器上从右到左分配位字段,在其他机器上从左到右分配.- 结束说明 ]

c ++ 11论文

因此布局取决于编译器实现,编译标志,目标拱等.刚检查了几个编译器和输出主要是8 8:

#include <stdint.h>
#include <iostream>

class Test32
{
    int64_t first : 40;
    int32_t second : 24;
};

class Test64
{
    int64_t first : 40;
    int64_t second : 24;
};

int main()
{
    std::cout << sizeof(Test32) << " " << sizeof(Test64);
}
Run Code Online (Sandbox Code Playgroud)