鍋あり谷あり

テーマを決めずに適当に書いています。

私の知っているC言語

今日は、C言語の意外かもしれない仕様をいくつか。

配列の添え字のように見えるもの

a[b] と書くと配列演算をしているような気分になるが、式の中に表れる a[b]は、(*( (a) + (b) ) ) の略記であり、ポインタの計算と何も変わるところがない*1
というわけで:

int a[] = {1,2,3,5,8};
int b[2][2]={{1,2},{5,9}};
a[3]=100;
printf( "%d\n", 3[a] ); /* 3[a] は、*(3+a)なので、a[3] と同じ*/
b[1][0]=999;
printf( "%d == %d\n", 1[b][0], 0[1[b]] ); /* どちらも b[1][0] と同じ */

となる。
(foo()?a:b)[3] と書くよりも 3[foo()?a:b] と書いた方が短くなるので、ソースコードを1文字でも短くしようというゲームに参加している人は知っていると役に立つことがあるかもしれない。
そうでない人の役には立たないと思う。

typedef は先頭に書かなくてもいい

これを知ったときは非常に驚いた。実は、typedef は先頭に書かなくてもいい。例えば以下の通り:

unsigned typedef char foo; /* たぶん正しいが、マイクロソフトコンパイラではエラー */
printf( "%d\n", (int)(foo)-1 ); /* y は unsigned char なので、255 が出力される */

g++ は、私の意図通りにコンパイルするが、マイクロソフトはエラーにしてくれる。エラーにしてくれた方がいいと思う。

関数へのポインタと関数

よく、関数へのポインタにそのまま括弧を付けて呼んだり、関数へのポインタへの代入文で、右辺が関数そのものだったりする。よく考えると、これはおかしい。
で。どれぐらいおかしいのかというと:

void bar(){ puts( "bar" );}
void test()
{
  void(*func)() = bar; /* 右辺は関数。左辺は関数へのポインタ。 */
  func(); /* bar を呼ぶ */
  (*func)();/* bar を呼ぶ */
  (**************func)();/* これでも bar を呼ぶ */
}

これぐらい。
これが合法であるという知識はあるが、なぜこんなものを合法にしたのかは知らない。


今日はこんなところで。
#写真と本文は関係ありません。

*1:宣言文の [ ] は、また別の話。