メンバ変数の並びを考慮すべし

メンバ変数の並びを考慮すべし

クラス(や構造体)のメンバ変数の並びによって、クラスのメモリサイズが節約出来る場合があります。

List1

class CClass{
public:
    char    m_nCharA;
    int     m_nIntA;
    char    m_nCharB;
    int     m_nIntB;
};

クラス CClass のメモリサイズは、16バイトです。
メンバ変数は、char が2つ、int が2つのなので、実際に使用しているのは10バイトのハズですが、
アラインされて16バイトになってしまっています。

これを避けてメモリ節約をする場合、リスト2のようにします。 

List2

class CClass{
public:
    int     m_nIntA;
    int     m_nIntB;
    char    m_nCharA;
    char    m_nCharB;
};

こうすることでクラス CClass のメモリサイズは、12バイトになりました。
しかしそれでも 2バイト余分なものが入っています。
ここまでで十分だと思うのですが、さらにこの2バイトを節約したい場合はリスト3のようにします。 

List3

#pragma pack( push )
#pragma pack( 1 )
    class CClass{
    public:
        int     m_nIntA;
        int     m_nIntB;
        char    m_nCharA;
        char    m_nCharB;
    };
#pragma pack( pop )

これで 10バイトに収まりました。
#pragma pack でアラインサイズを1バイトに変えてしまうことで、余白(パディング)が入らないように指示してます。

もちろん、リスト1のままでも #pragma pack をしてしまえば、サイズは最小に収まりますが、それでは速度面で最適とはいえません。
コンパイラがアラインを行うのは、プロセッサのメモリアクセスが最適に出来るようにする為のものなので、むやみに #pragma pack を乱用するのは禁物と思われます。
サイズ縮小によるメモリ節約というメリットと、区切りの悪いメモリ配置によるアクセス速度低下というデメリットを天秤にかける必要があります。


というわけですが、まず言えることは、メンバ変数を定義する際には、出来るだけ各値が最適なサイズ境界に配置され、なおかつ余白(パディング)が入らないように考慮するのが吉と思われます。

int long は4バイト境界。
short は2バイト境界。
char bool は1バイトなのでどこでもOK。 

ご指摘いただきました。どうもありがとう。(念のため匿名さん 2005/4/7)

> sizeof bool は実装依存なので 1 ではない事があるよ。
ごもっともです。説明が足りませんでした。すみません。
今の言語規格では、bool以外にも char 以外の全ての型の実サイズはコンパイラ依存になっており、大小関係しか決められてません。
ですので、ここに紹介してあるサイズが全ての処理系で正しい訳ではないということを補足させて下さい。

ご指摘いただきました。どうもありがとう。(念のため匿名さん 2005/4/7)

>> メンバ変数の並びを考慮すべし
> これは初期化される順番を先に考慮すべきだと思います。


これは、メンバ変数の初期化順序に依存関係がある場合においては、そのような並びにしないとダメですよ。
との事です。最初は何のことか分かりませんでした。猛省。

たとえば、以下のようなクラスは、メモリ節約を無視してでも、この並びにしないと期待通りの値になりません。
(少々強引なコードですが、良い例が思いつかない・・・。)

class CClass{
public:
    short   m_count; // 初期化リストは、メンバの定義順に処理される為、
    int     m_size1; // この順序を崩すと、期待通りの値にならない。
    short   m_size2; // この場合はパディング覚悟でこの順序にする必要がある。

    CClass(int nCount)
        :m_count(nCount)
        ,m_size1(m_count * sizeof(int))
        ,m_size2(m_size1 * sizeof(int))
        {}
};

なお、これは初期化リストの処理順序の話なので、初期化リストじゃない箇所(コンストラクタ内)で初期化処理を記述すれば、並びがどうあれ期待通りにはなります。
がしかし初期化リストに書くことによるメリットもあると思うので、そのあたりも考慮する必要がありそうです。





サイト名: シンクリッジ   http://www.thinkridge.com
この記事のURL:   http://www.thinkridge.com/modules/tinyd1/index.php?id=5