Romantic Irony

好きなこと・気になったことについて書いていきます!

C言語の参照外しとダブルポインタ

ふと、「参照外し」の語源というか由来というかそんなものは何かあるのかな〜と思いググってたところ別のある問題に出くわした。 これってみんなパッと答えられるのかなと思い書いてみた。 そのソースコードが以下である。

gistd9a5cbadb0c86a3adb99cb94b95302a5

問題はこれの出力を答えろというもの。

char型の配列とポインタ変数が宣言してある。 そして、ポインタ変数に配列のアドレスが代入され、出力してある。 という簡単なC言語のプログラムである。 慣れてる人ならこんな問題は瞬殺なんだろうけど、自分は少し考えてしまった... ということで、整理も兼ねて解答を書いていく。

解答

まず1つ目の出力から。
char mark_1[][5] = {"zyx", "wv", "utsr", "qpo", "nmlk"};
まずこれは普通にchar型の二次元配列を宣言して初期化しているだけ。
printf("%s¥n", mark_1[1] + 1);
出力ではフォーマットパラメータに%sが指定されている。(文字列を出力) その出力の値としてmark_1[1] + 1が指定されている。
これはまずmark_1[1]で配列の要素"wv"のアドレスを指定し、そのあと"wv"要素の中の先頭から+1個進んだ先の文字列"v"が出力される。

2つ目の出力
printf("%s¥n", pt_1 + 1);
これも1つ目と同様の考え方で理解できる。ただ、今度はmark_1を直接使っているのではなくpt_1にmark_1のアドレスを代入してそれを利用している。この場合pt_1ではすでに*mark_1が代入されているので、配列mark_1の先頭要素であることが確定している。(1つ目と表記をあわせるならpt_1=*mark_1=mark_1[0])そして要素内で+1してあるので、結果出力は"yx"になる。

3つ目
ここはもう1つの配列
char *mark_2[5] = {"cba", "ed", "ihgf", "lkj", "ponm"};
を使っている。そして
pt_2 = mark_2;
で配列mark_2の先頭アドレスをpt_2に代入。
printf("%s¥n", *pt_2 + 2);
ここでは*pt_2で先頭の要素"cba"を指しており、+2で先頭から2つ進んだ先の"a"を出力している。

4つ目
printf("%c¥n", **pt_2 + 2);
ここがちょっと特殊である。といっても難しくはない。フォーマットパラメータは%cとなっている。(1文字を出力)そしてその値として**pt_2+2となっている。この考え方としては、参照外しの演算子「*」と四則演算子では参照外しの演算子が優先されるということと、値として特定の文字が確定したあとに四則演算を用いるとASCIIコードの計算とされることだ。具体的に言うと、まず**pt_2で配列mark_2の先頭要素の先頭文字"c"が確定する。この時点で+2はポインタ演算ではなくなりASCIIコードでの+2となる。そのため"c"の2つ先のアルファベット"e"が出力されることになる。間違っても配列の要素の"ed"の"e"が出力されたわけではない!

5つ目
printf("%s¥n", *(pt_2 + 2) + 2);
pt_2+2してから参照外しをしているので配列要素の"ihgf"を指しており、その後さらに+2しているので"gf"が出力される。

これで終わり。

まとめ

参照外しや[]の優先度はあまり気にしたことがなかったので以下のサイトで確認してみた。

C Operator Precedence - cppreference.com

すると、やはり優先度はa[ ] > *a となっている。そして四則演算子よりも参照外しの方が優先度が高いことがわかる。
言われてみれば当たり前な話だけど、演算子の優先度に気をつけよう。そしてなにより基本は大事!