簡體   English   中英

當int64_t更改為int32_t時,為什么類大小會增加

[英]Why class size increases when int64_t changes to int32_t

在我的第一個例子中,我有兩個使用int64_t位域。 當我編譯並獲得類的大小時,我得到8。

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

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

但是當我將第二個bitfeild更改為int32_t時,類的大小加倍為16:

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

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

這種情況發生在GCC 5.3.0和MSVC 2015上。但為什么呢?

在你的第一個例子中

int64_t first : 40;
int64_t second : 24;

firstsecond使用單個64位整數的64位。 這會導致類的大小為單個64位整數。 在你的第二個例子中

int64_t first : 40;
int32_t second : 24;

這是兩個獨立的位字段存儲在兩個不同的內存塊中。 您使用64位整數的40位,然后使用24位另一個32位整數。 這意味着您至少需要12個字節(此示例使用8位字節)。 很可能你看到的額外4個字節是填充以在64位邊界上對齊類。

正如其他答案和評論所指出的那樣,這是實現定義的行為,您可以/將在不同的實現上看到不同的結果。

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“)但我知道沒有計划在標准中定義這樣的東西。

添加其他人已經說過的內容:

如果要檢查它,可以使用編譯器選項或外部程序來輸出結構布局。

考慮這個文件:

// 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 (其中-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)
    +---
  • 鐺:
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 I版本( Rextester使用的版本 )似乎默認將兩個位域優化為單個變量,並且我不確定如何禁用此行為。

標准說:

§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);
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM