読者です 読者をやめる 読者になる 読者になる

studies

いろいろ勉強用備忘録的な感じ

void *p = &p : 自分自身を参照する初期化の問題

C/C++

久しぶりの投稿はちょっと変な話。

次のように、自分自身を参照するポインタを作ってみる。

#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ではそうもいかない。
これはけっこう厄介だと思う。