たとえば、Tempというクラスがあるとします。
class Temp
{
public:
int function1(int foo) { return 1; }
void function2(int bar) { foobar = bar; }
private:
int foobar;
};
Tempクラスのオブジェクトを作成するとき、必要なスペースの量をどのように計算し、メモリ内でどのように表されますか(例:foobarの場合は4バイト| function1の場合は8バイト|など|)
一次近似では、オブジェクトのサイズは、その構成データメンバーのサイズの合計です。これよりも小さくなることはありません。
より正確には、コンパイラは、データメンバー間にパディングスペースを挿入して、各データメンバーがプラットフォームのアライメント要件を満たしていることを確認する権利があります。一部のプラットフォームはアライメントについて非常に厳格ですが、他のプラットフォーム(x86)はより寛容ですが、適切なアライメントで大幅にパフォーマンスが向上します。そのため、コンパイラの最適化設定でさえ、オブジェクトのサイズに影響を与える可能性があります。
継承と仮想関数は、さらに複雑になります。他の人が言ったように、クラス自体のメンバー関数は「オブジェクトごと」のスペースを占有しませんが、そのクラスのインターフェイスに仮想関数が存在することは、一般に仮想テーブルの存在を意味します。適切な関数実装を動的に解決して、実行時に呼び出します。通常、仮想テーブル(vtbl)は、各オブジェクトに格納されたポインターを介してアクセスされます。
派生クラスオブジェクトには、基本クラスのすべてのデータメンバーも含まれます。
最後に、アクセス指定子(public、private、protected)は、コンパイラーにデータメンバーのパッキングに一定の余裕を与えます。
簡単な答えは、sizeof(myObj)またはsizeof(MyClass)は常にオブジェクトの適切なサイズを教えてくれますが、その結果は必ずしも簡単に予測できるものではないということです。
sizeof(Temp)
サイズがわかります。ほとんどの場合、4バイト(多くの仮定が与えられている)であり、これはint専用です。関数はオブジェクトごとにスペースを占有せず、一度コンパイルされ、使用されるたびにコンパイラーによってリンクされます。
オブジェクトのレイアウトが何であるかを正確に言うことは不可能ですが、標準ではオブジェクトのバイナリ表現を定義していません。
構造パディング のようなものが原因で、データメンバーのバイトの合計であるとは限らないなど、バイナリ表現で注意すべきことがいくつかあります
私はいつもこの種のことを疑問に思っていたので、完全な答えを出すことにしました。それはあなたが期待するかもしれないものについてであり、それは予測可能です(イェーイ)!したがって、以下の情報を使用すると、クラスのサイズを予測できるはずです。
Visual Studio Community 2017(バージョン15.2)を使用して、リリースモードですべての最適化を無効にし、RTTI( Run-time Type Information )off、決定しました以下:
簡単な答え:
まず第一に:
<size of pointer> == 4
バイト<size of pointer> == 8
バイトclass ChildClass: virtual public ParentClass
今、私の調査結果は次のとおりです。
<size of variable>
バイトになります<size of variables>
バイトです<size of pointer>
バイトです<size of pointer>
バイトになります<size of pointer>
バイトにします<size of pointer>
バイト、 wrappingすべてのメンバー変数は、<size of pointer>
をカバーするために必要な<total size of member variables>
バイト単位で増加します。正しい...(Conclusions...で何が起こっているのかを推測してください)注、function()にテキストを入力し、クラスのインスタンスを作成し、関数を呼び出してみました。関数クラスのサイズは変更されません(最適化ではありません)!少し驚きましたが、実際には意味があります。メンバー関数は変更されないため、クラス自体の外部に保存できます。
結論:
<size of pointer>
バイトを消費し、<size of pointer>
バイトを上記のクラスに追加します。もちろん、このvtableはクラスごとに1つしか存在できません(存在するかしないか)。<size of pointer>
バイト単位で増加します。それほど多くのメモリを消費する必要はありません。<size of pointer>
バイトのメモリごとにvtable "ヘルパーブロック"を追加しているためだと思います...長答:
このコードを使用して、これをすべて決定しました。
#include <iostream>
using namespace std;
class TestA
{
};
class TestB: public TestA
{
};
class TestC: virtual public TestA
{
};
class TestD
{
public:
int i;
};
class TestE: public TestD
{
public:
int j;
};
class TestF: virtual public TestD
{
public:
int j;
};
class TestG
{
public:
void function()
{
}
};
class TestH: public TestG
{
public:
void function()
{
}
};
class TestI: virtual public TestG
{
public:
void function()
{
}
};
class TestJ
{
public:
virtual void function()
{
}
};
class TestK: public TestJ
{
public:
void function() override
{
}
};
class TestL: virtual public TestJ
{
public:
void function() override
{
}
};
void main()
{
cout << "int:\t\t" << sizeof(int) << "\n";
cout << "TestA:\t\t" << sizeof(TestA) << "\t(empty class)\n";
cout << "TestB:\t\t" << sizeof(TestB) << "\t(inheriting empty class)\n";
cout << "TestC:\t\t" << sizeof(TestC) << "\t(virtual inheriting empty class)\n";
cout << "TestD:\t\t" << sizeof(TestD) << "\t(int class)\n";
cout << "TestE:\t\t" << sizeof(TestE) << "\t(inheriting int + int class)\n";
cout << "TestF:\t\t" << sizeof(TestF) << "\t(virtual inheriting int + int class)\n";
cout << "TestG:\t\t" << sizeof(TestG) << "\t(function class)\n";
cout << "TestH:\t\t" << sizeof(TestH) << "\t(inheriting function class)\n";
cout << "TestI:\t\t" << sizeof(TestI) << "\t(virtual inheriting function class)\n";
cout << "TestJ:\t\t" << sizeof(TestJ) << "\t(virtual function class)\n";
cout << "TestK:\t\t" << sizeof(TestK) << "\t(inheriting overriding function class)\n";
cout << "TestL:\t\t" << sizeof(TestL) << "\t(virtual inheriting overriding function class)\n";
cout << "\n";
system("pause");
}
出力:
32(x86)ビット:
int: 4
TestA: 1 (empty class)
TestB: 1 (inheriting empty class)
TestC: 4 (virtual inheriting empty class)
TestD: 4 (int class)
TestE: 8 (inheriting int + int class)
TestF: 12 (virtual inheriting int + int class)
TestG: 1 (function class)
TestH: 1 (inheriting function class)
TestI: 4 (virtual inheriting function class)
TestJ: 4 (virtual function class)
TestK: 4 (inheriting overriding function class)
TestL: 8 (virtual inheriting overriding function class)
64(x64)ビット:
int: 4
TestA: 1 (empty class)
TestB: 1 (inheriting empty class)
TestC: 8 (virtual inheriting empty class)
TestD: 4 (int class)
TestE: 8 (inheriting int + int class)
TestF: 24 (virtual inheriting int + int class)
TestG: 1 (function class)
TestH: 1 (inheriting function class)
TestI: 8 (virtual inheriting function class)
TestJ: 8 (virtual function class)
TestK: 8 (inheriting overriding function class)
TestL: 16 (virtual inheriting overriding function class)
多重継承に関する情報が必要な場合は、自分のことを考えてみてください! -.-
実行時にメモリ内でオブジェクトがどのように表現されるかについての詳細情報が必要な場合は、ABI( Application Binary Interface )仕様を参照してください。コンパイラが実装しているABIを確認する必要があります。たとえば、GCCバージョン3.2以降は Itanium C++ ABI を実装しています。
メソッドは、特定のインスタンス化されたオブジェクトではなく、クラスに属します。
仮想メソッドがない限り、オブジェクトのサイズは、その非静的メンバーのサイズの合計に、位置合わせのためのメンバー間のオプションのパディングを加えたものです。メンバーはおそらくメモリ内で順番にレイアウトされますが、仕様では、アクセス仕様が異なるセクション間の順序や、スーパークラスのレイアウトに対する順序は保証されていません。
仮想メソッドが存在する場合、 vtable およびその他のRTTI情報のために追加のスペースが必要になる場合があります。
ほとんどのプラットフォームでは、実行可能コードは、実行可能ファイルまたはライブラリの読み取り専用.text
(または同様の名前)セクションに配置され、どこにもコピーされません。 class Temp
にメソッドpublic: int function1(int)
がある場合、Temp
メタデータには、実際の実装用の_ZN4Temp9function1Ei
(コンパイラーによってマングル名は異なる場合があります)関数へのポインターが含まれる場合がありますが、埋め込まれた実行可能コード。
メンバー関数は、特定のクラスのオブジェクトのサイズを考慮しません。オブジェクトのサイズは、メンバー変数のみに依存します。仮想関数を含むクラスの場合、VPTRはオブジェクトレイアウトに追加されます。したがって、オブジェクトのサイズは、基本的にメンバー変数のサイズ+ VPTRのサイズです。コンパイラーはDWORD境界でメンバー変数を見つけようとするため、これは正しくない場合があります。
Microsoft Visual C++を使用している場合、オブジェクトの実際の大きさを示す1つのコンパイラオプションがあります:/ d1reportSingleClassLayout
Lavavejによるこのビデオを除き、文書化されていません http://channel9.msdn.com/Shows/Going+Deep/C9-Lectures-Stephan-T-Lavavej-Advanced-STL-3-of-n
特定の構造のレイアウトを調べたい場合は、offsetof(s,member)
マクロも使用できます。特定のメンバーが住んでいる構造体のベースアドレスからどれだけ離れているかがわかります。
struct foo {
char *a;
int b;
};
// Print placement of foo's members
void printFoo() {
printf("foo->a is %zu bytes into a foo\n", offsetof(struct foo, a));
printf("foo->b is %zu bytes into a foo\n", offsetof(struct foo, b));
}
int main() {
printFoo();
return 0;
}
典型的な32ビットマシンで印刷します:
foo->a is 0 bytes into a foo
foo->b is 4 bytes into a foo
一方、典型的な64ビットマシンでは、印刷されます
foo->a is 0 bytes into a foo
foo->b is 8 bytes into a foo
これ が役立つ場合があります。
さらに、クラス関数は他の関数と同様に表されます。 C++が関数に対して行う唯一の魔法は、関数名をマングルして、特定のクラス内の特定のパラメーターセットで特定の関数を一意に識別することです。
pahole
( 'Poke-A-HOLE' )のユーティリティ呼び出しがあります。これは、名目上はオブジェクトレイアウトのパディング方法を調べることを目的としていますが、オブジェクトのサイズとレイアウトを視覚化するのに最適です。