28

私の最初の例では、int64_t. コンパイルしてクラスのサイズを取得すると、8 になります。

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

int main()
{
    std::cout << sizeof(Test); // 8
}

しかし、2 番目のビットフィールドをint32_tクラスのサイズに変更すると、2 倍の 16 になります。

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

int main()
{
    std::cout << sizeof(Test); // 16
}

これは、GCC 5.3.0 と MSVC 2015 の両方で発生します。しかし、なぜでしょうか?

4

4 に答える 4

37

あなたの最初の例では

int64_t first : 40;
int64_t second : 24;

と の両方で、単一の 64 ビット整数の 64 ビットを使用しますfirstsecondこれにより、クラスのサイズが単一の 64 ビット整数になります。2番目の例では、

int64_t first : 40;
int32_t second : 24;

これは、メモリの 2 つの異なるチャンクに格納されている 2 つの別個のビット フィールドです。64 ビット整数の 40 ビットを使用してから、別の 32 ビット整数の 24 ビットを使用します。これは、少なくとも 12 バイトが必要であることを意味します (この例では 8 ビット バイトを使用しています)。ほとんどの場合、表示される余分な 4 バイトは、クラスを 64 ビット境界に揃えるためのパディングです。

他の回答とコメントが指摘しているように、これは実装定義の動作であり、異なる実装では異なる結果が表示される可能性があります。

于 2016-07-12T17:58:54.933 に答える
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;

Visual Studio /d1reportSingleClassLayoutX(Xクラス名または構造体名の全部または一部) や Clang++ -Xclang -fdump-record-layouts( GCC フロントエンド コマンドではなく Clang フロントエンド コマンドとして-Xclang解釈するようにコンパイラに指示する) などのレイアウト出力フラグを使用する-fdump-record-layoutsと、ダンプできます。Test_1およびTest_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)
    +---
  • クラン:
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>
]>

この出力を生成するために使用した Clang のバージョン ( Reextesterで使用されたもの) は、デフォルトで両方のビットフィールドを単一の変数に最適化するように見えることに注意してください。この動作を無効にする方法がわかりません。

于 2016-07-12T21:26:55.707 に答える
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);
}
于 2016-07-12T18:47:05.427 に答える