void *p = &p : 自分自身を参照する初期化の問題
久しぶりの投稿はちょっと変な話。
次のように、自分自身を参照するポインタを作ってみる。
#include <stdio.h> int main(void) { void *p = &p; printf("%p\n", p); return 0; }
自分の環境では、普通にコンパイルが通る。
そして、実行するたびに結果が変わる(実行のたびに別のアドレスにpが配置される)。
こんなプログラムは何の意味もないと思うかもしれないが、次のような場合はどうだろうか?
typedef struct List { char value; struct List *next; } List; static List terminator = {0, &terminator}; List * const tail = &terminator;
terminatorは自分自身を参照するセルである。
つまり、tailは要素数1の環状リストである。
ふつう、線形リストの最後はNULLを指すことで終端を示すが、このような要素数1の環状リストで最後を「結んで」やると、簡潔にコードをかける場合がある(と思う)。
というのは、こうすることで、NULLのnextを参照してしまう恐れがなくなるからだ。
しかし、この書き方には疑問が残る。
つまり、宣言と同時に初期化する場合、実行順序として、「左辺のメモリの確保」→「右辺の評価」となっていることは保証されているのか、という問題である。
void *p = &p;
が許されるということは、私のコンパイル環境ではその実行順序で正しいようだが、これには一般性があるだろうか?
つまり、規格的にはどうだろうか?
少しネットを探してみたが、それらしい記事を見つけることはできなかった。
もし、この実行順序が保証されないとすると、少し厄介なことも起こる。
static List terminator; List * const tail = &terminator; void initialize_tail(void) { tail->value = 0; tail->next = tail; }
terminatorとtailを宣言した後で、ユーザにtailの初期化関数initialize_tail()を呼び出してもらわなければならなくなる。
(関数の「外」に代入文はかけないため)。
C#とかなら、terminatorやtailをListクラスの静的メンバとして、静的コンストラクタを用いて初期化すればよさそうだが、Cではそうもいかない。
これはけっこう厄介だと思う。