技術ブログとか

C/C++/C#/DirectX/Effect

null pointer

手伝っていたゲームプログラムが一段落ついたのでブログ再開。

疲れているので今回は小ネタっぽい何か。

 

C言語的に言うならNULL、C++C++11)的に言うならnullptrというものがある。

今更説明するまでもないが、何のオブジェクトも指さないことが保障されているアドレスである。

ポインタ型を必要とする場面で0と書いた場合も、nullを指す。

というかNULLは0をdefineしたものである。

// 処理系によって定義はバラバラ
#define NULL 0
#define NULL (void*)0
#define NULL ((void*)0l)

で、このnullを指している状態のポインタ変数をnull pointerと呼ぶ。

1つ注意する点があるとすれば、ポインタ型が必要な場面で0と書けば確かにそれはnullを示すが、nullの実際のアドレスが0番とは限らないということ。

つまり、nullのアドレス自体は0xffffffff0x12345678である可能性も十分に有り得るということである。

 

で、このnullは何も指さないので、アクセスしようとするとセグフォ吐いて落ちる。

class Hoge
{
public:
    // setter
    void x(int val)
    {
        _x = val;
    }
    // getter
    int x()
    {
        return _x;
    }

private:
    int _x;
};

int main()
{
    // OK
    Hoge *hoge = new Hoge();
    hoge->x(1234);
    cout << hoge->x() << endl;

    // deleteしたらnullを代入
    delete hoge;
    hoge = nullptr;

    // ERROR!
    hoge->x(5678);
    cout << hoge->x() << endl;
}

このように、基本的にnullが代入されていればそのポインタは使えない――と思っていたら大間違いだった。

ここからが今回の本題。

// 適当なクラス
class Hoge
{
public:
    // hello, world
    void hello()
    {
        cout << "Hello, world!" << endl;
    }
};

int main()
{
    Hoge *hoge = nullptr;

    // OK
    hoge->hello();
}

なんとこのコード、動いてしまう。

メンバ関数の実体はインスタンスとは別の場所にあるため、アドレスの先が存在しなくても何の問題もないらしい。

メンバ関数内でのdelete thisが正常に動作するのと同じ理由である。

また、メンバ関数の中でメンバ変数にアクセスしている場合は、もちろん呼び出すことはできない。

ただし、メモリ領域にアクセスしなければいいので、以下のようなこともできる。

// 適当なクラス
class Hoge
{
public:
    int x;
    int y;
    int z;
};

int main()
{
    Hoge *hoge = nullptr;

    // OK
    cout << &hoge->x << endl;
    cout << &hoge->y << endl;
    cout << &hoge->z << endl;
}

ポインタについては大体わかった気になっていたが、どうやらまだまだ知らないことも多そうだ。

 

ちなみにこのことを知ることになったきっかけは、構造体のメンバのoffsetの求め方を調べようとしたことだった。

構造体メンバのoffsetは、sizeofの合計だけで計算するとパディングの影響でずれてしまう。

そのため、正確な値の求め方について調べると、cstddefで定義されているoffsetofを使えば良いことがわかった。

で、このoffsetofと関連してXtOffsetの実装について調べ、その中でnullを介してメンバ変数のアドレスを参照していたのでこれにたどり着くこととなった。

非常に面白い。