こんなに気色悪い! C言語のシンタックスシュガー

こんなに気色悪い! C言語のシンタックスシュガー

C言語にはシンタックスシュガーというものがあります。

シンタックスシュガーというのは、「正式な書き方はこれだけど、別の書き方も提供しているよ!」というものです。

実世界で例えてみるなら「🍪(←ビスケットの絵文字)はビスケットと呼ばれているけれど、クッキーとも呼ばれているよ!」みたいなものです。

シンタックスシュガーという名前は、なんだか甘そうに聞こえますが、別に甘くありません。

C言語/C++にあるシンタックスシュガー

ところで、プログラミング言語であるC言語/C++にも同様に、シンタックスシュガーが存在します。

さっそく、例を見てみましょう。その前にまず、配列を用意します。

#include<stdio.h>

int main(int argc, char** argv) {

    int size = 6;
    int next[size];

    for (int i=0; i<size-1; ++i) {
        next[i] = i+1;
    }
    next[size] = 0;
}

サイズが6の単方向配列nextを用意しました。例えばnext[0]==1だし、next[4]==5となっています。また、配列の最後尾はnext[5]==0と、先頭を指すようにしています。

では、シンタックスシュガーをどうぞ。

...
    int i=3;
    printf("next[%d]            == %d\n", i, next[i]);
    // next[3]            == 4
    printf("%d[next]            == %d\n", i, i[next]);
    // 3[next]            == 4
...

なんだこれは

配列といえば、おなじみの書き方であるarray[4]が主流です。

しかし、実はC言語ではこのように、4[array]という実に可読性皆無な書き方ができてしまいます。なぜか。

これは、C言語を勉強したことがある人なら、一度は見たことがあるであろう、この式に由来しています。

...
    int i=3;
    printf("next[%d]            == %d\n", i, next[i]);
    printf("*(next+%d)          == %d\n", i, *(next+i));
    printf("*(%d+next)          == %d\n", i, *(i+next));
    printf("%d[next]            == %d\n", i, i[next]);
...

そう。「こんなの、いつ使うんだよ……」と思ったことが大多数だと考えられる、next[i]==*(next+i)という構文。

指定したnextのアドレスにi*sizeof(int)バイト分だけ足しこんでから、さらに参照演算子で値を取り出すという手法です。

この*(next+i)という式の中にある足し算は入れ替えることが可能なので、*(i+next)という風に書き換えられます。

さらに、先ほどの「こんなの、いつ使うんだよ……」公式を用いて、*(i+next)==i[next]という風にでできるわけです。

なぜこんなものがあるのか

C言語は、コンパイルする際に、中間言語であるアセンブリ言語に翻訳されます。

その際に、アドレスの計算には、inextの足し算の順序は、交換しても差し支えないようになっています。

だから、next[i]==i[next]のような読みにくいったらありゃしない書き換えも可能になってしまっているのです。

参考までに

試しに、next[0]next[next[i]]では、どのようになるのかも確かめてみました。

#include<stdio.h>

int main(int argc, char** argv) {

    int size = 6;
    int next[size];

    for (int i=0; i<size-1; ++i) {
        next[i] = i+1;
    }
    next[size] = 0;

    int i=3;
    printf("next[%d]            == %d\n", i, next[i]);
    // next[3]            == 4
    printf("*(next+%d)          == %d\n", i, *(next+i));
    // *(next+3)          == 4
    printf("*(%d+next)          == %d\n", i, *(i+next));
    // *(3+next)          == 4
    printf("%d[next]            == %d\n", i, i[next]);
    // 3[next]            == 4

    printf("next[%d]            == %d\n", 0, next[0]);
    // next[0]            == 1
    printf("*(next+%d)          == %d\n", 0, *(next+0));
    // *(next+0)          == 1
    printf("*next              == %d\n",     *next);
    // *next              == 1

    printf("next[next[%d]]      == %d\n", i, next[next[i]]);
    // next[next[3]]      == 5
    printf("*(next+ *(next+%d)) == %d\n", i, *(next+ *(next+i)));
    // *(next+ *(next+3)) == 5
    printf("*(*(%d+next)+next)  == %d\n", i, *(*(i+next)+next));
    // *(*(3+next)+next)  == 5
    printf("*(%d[next]+next)    == %d\n", i, *(i[next]+next));
    // *(3[next]+next)    == 5
    printf("%d[next][next]      == %d\n", i, i[next][next]);
    // 3[next][next]      == 5

    return 0;
}

なるほど、読みにくい。

さいごに

リーダブルコードを書くためには、少々道草を食むような真似ですが、このように、読みにくいことを学べば、反面教師的になれるかと思います。

他人に「講義課題のコード、書いてくれよ」と言われたときはともかくとして、自分で作成する場合は、このような読みにくいコードはあまり書かないようにしましょう。