web-dev-qa-db-ja.com

C ++でオブジェクトのサイズをどのように決定しますか?

たとえば、Tempというクラスがあるとします。

class Temp
{
    public:
        int function1(int foo) { return 1; }
        void function2(int bar) { foobar = bar; }

    private:
        int foobar;
};

Tempクラスのオブジェクトを作成するとき、必要なスペースの量をどのように計算し、メモリ内でどのように表されますか(例:foobarの場合は4バイト| function1の場合は8バイト|など|)

38
Joel

一次近似では、オブジェクトのサイズは、その構成データメンバーのサイズの合計です。これよりも小さくなることはありません。

より正確には、コンパイラは、データメンバー間にパディングスペースを挿入して、各データメンバーがプラットフォームのアライメント要件を満たしていることを確認する権利があります。一部のプラットフォームはアライメントについて非常に厳格ですが、他のプラットフォーム(x86)はより寛容ですが、適切なアライメントで大幅にパフォーマンスが向上します。そのため、コンパイラの最適化設定でさえ、オブジェクトのサイズに影響を与える可能性があります。

継承と仮想関数は、さらに複雑になります。他の人が言ったように、クラス自体のメンバー関数は「オブジェクトごと」のスペースを占有しませんが、そのクラスのインターフェイスに仮想関数が存在することは、一般に仮想テーブルの存在を意味します。適切な関数実装を動的に解決して、実行時に呼び出します。通常、仮想テーブル(vtbl)は、各オブジェクトに格納されたポインターを介してアクセスされます。

派生クラスオブジェクトには、基本クラスのすべてのデータメンバーも含まれます。

最後に、アクセス指定子(public、private、protected)は、コンパイラーにデータメンバーのパッキングに一定の余裕を与えます。

簡単な答えは、sizeof(myObj)またはsizeof(MyClass)は常にオブジェクトの適切なサイズを教えてくれますが、その結果は必ずしも簡単に予測できるものではないということです。

62
Drew Hall
sizeof(Temp)

サイズがわかります。ほとんどの場合、4バイト(多くの仮定が与えられている)であり、これはint専用です。関数はオブジェクトごとにスペースを占有せず、一度コンパイルされ、使用されるたびにコンパイラーによってリンクされます。

オブジェクトのレイアウトが何であるかを正確に言うことは不可能ですが、標準ではオブジェクトのバイナリ表現を定義していません。

構造パディング のようなものが原因で、データメンバーのバイトの合計であるとは限らないなど、バイナリ表現で注意すべきことがいくつかあります

19
Todd Gardner

私はいつもこの種のことを疑問に思っていたので、完全な答えを出すことにしました。それはあなたが期待するかもしれないものについてであり、それは予測可能です(イェーイ)!したがって、以下の情報を使用すると、クラスのサイズを予測できるはずです。

Visual Studio Community 2017(バージョン15.2)を使用して、リリースモードですべての最適化を無効にし、RTTI( Run-time Type Information )off、決定しました以下:


簡単な答え:

まず第一に:

  • 32(x86)ビットで、<size of pointer> == 4バイト
  • 64(x64)ビットで、<size of pointer> == 8バイト
  • 「仮想クラスの継承」と言うときは、たとえば:class ChildClass: virtual public ParentClass

今、私の調査結果は次のとおりです。

  • 空のクラスは1バイトです
  • 空のクラスの継承はまだ1バイトです
  • 関数を含む空のクラスはまだ1バイトです(?!(を参照)
  • 関数を持つ空のクラスの継承はまだ1バイトです
  • 空のクラスに変数を追加すると、<size of variable>バイトになります
  • クラスを変数で継承し、別の変数を追加することは<size of variables>バイトです
  • クラスを継承し、その機能をオーバーライドすると、vtable(Conclusionsセクションで説明されている)が追加され、<size of pointer>バイトです
  • 関数virtualを宣言するだけでvtableも追加され、<size of pointer>バイトになります
  • 空のクラスの仮想クラス継承(メンバー関数の有無にかかわらず)もvtableを追加し、クラスを<size of pointer>バイトにします
  • 空でないクラスの仮想クラスの継承もvtableを追加しますが、やや複雑になります:itadds<size of pointer>バイト、 wrappingすべてのメンバー変数は、<size of pointer>をカバーするために必要な<total size of member variables>バイト単位で増加します。正しい...(Conclusions...で何が起こっているのかを推測してください)

、function()にテキストを入力し、クラスのインスタンスを作成し、関数を呼び出してみました。関数クラスのサイズは変更されません(最適化ではありません)!少し驚きましたが、実際には意味があります。メンバー関数は変更されないため、クラス自体の外部に保存できます。

結論:

  • 空のクラスは1バイトです。これは、メモリ内に存在するために最低限必要なクラスだからです。 ただし、データまたはvtableデータが追加されると、0バイトからカウントを開始します。
  • メンバー関数は外部に保存されるため、(non-virtual)メンバー関数を追加してもサイズには何も影響しません。
  • (クラスがオーバーライドされていなくても)メンバー関数を仮想に宣言するか、子クラスのメンバー関数をオーバーライドすると、 "vtable"または "virtual function table" と呼ばれるものが追加されます。 for Dynamic Dispatch (ただし、使用するのは本当にすばらしく、使用することを強くお勧めします)。このvtableは<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)

多重継承に関する情報が必要な場合は、自分のことを考えてみてください! -.-

10
Andrew

実行時にメモリ内でオブジェクトがどのように表現されるかについての詳細情報が必要な場合は、ABI( Application Binary Interface )仕様を参照してください。コンパイラが実装しているABIを確認する必要があります。たとえば、GCCバージョン3.2以降は Itanium C++ ABI を実装しています。

8
Paul Morie

メソッドは、特定のインスタンス化されたオブジェクトではなく、クラスに属します。

仮想メソッドがない限り、オブジェクトのサイズは、その非静的メンバーのサイズの合計に、位置合わせのためのメンバー間のオプションのパディングを加えたものです。メンバーはおそらくメモリ内で順番にレイアウトされますが、仕様では、アクセス仕様が異なるセクション間の順序や、スーパークラスのレイアウトに対する順序は保証されていません。

仮想メソッドが存在する場合、 vtable およびその他のRTTI情報のために追加のスペースが必要になる場合があります。

ほとんどのプラットフォームでは、実行可能コードは、実行可能ファイルまたはライブラリの読み取り専用.text(または同様の名前)セクションに配置され、どこにもコピーされません。 class Tempにメソッドpublic: int function1(int)がある場合、Tempメタデータには、実際の実装用の_ZN4Temp9function1Ei(コンパイラーによってマングル名は異なる場合があります)関数へのポインターが含まれる場合がありますが、埋め込まれた実行可能コード。

6
ephemient

メンバー関数は、特定のクラスのオブジェクトのサイズを考慮しません。オブジェクトのサイズは、メンバー変数のみに依存します。仮想関数を含むクラスの場合、VPTRはオブジェクトレイアウトに追加されます。したがって、オブジェクトのサイズは、基本的にメンバー変数のサイズ+ VPTRのサイズです。コンパイラーはDWORD境界でメンバー変数を見つけようとするため、これは正しくない場合があります。

4
Canopus

Microsoft Visual C++を使用している場合、オブジェクトの実際の大きさを示す1つのコンパイラオプションがあります:/ d1reportSingleClassLayout

Lavavejによるこのビデオを除き、文書化されていません http://channel9.msdn.com/Shows/Going+Deep/C9-Lectures-Stephan-T-Lavavej-Advanced-STL-3-of-n

3
Johannes Gerer

特定の構造のレイアウトを調べたい場合は、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
2
Phil Miller

これ が役立つ場合があります。

さらに、クラス関数は他の関数と同様に表されます。 C++が関数に対して行う唯一の魔法は、関数名をマングルして、特定のクラス内の特定のパラメーターセットで特定の関数を一意に識別することです。

0
sybreon

pahole'Poke-A-HOLE' )のユーティリティ呼び出しがあります。これは、名目上はオブジェクトレイアウトのパディング方法を調べることを目的としていますが、オブジェクトのサイズとレイアウトを視覚化するのに最適です。

0
Phil Miller