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のアドレス自体は0xffffffff
や0x12345678
である可能性も十分に有り得るということである。
で、この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を介してメンバ変数のアドレスを参照していたのでこれにたどり着くこととなった。
非常に面白い。