constの位置の覚え方
constの置く位置はいつも「どこに置くんだっけな?」と迷うものですが、僕の覚え方を紹介したいと思います。C言語の文献やサイトでは特に言及されているのを見たことがないので、内容の正しさには少し自信がありません。ひょっとするとコンパイラの作成者の中では当たり前の事なのかもしれないし、はたまた見当違いなことなのかもしれませんので予めご了承下さい。
どういったケースで頭を悩ますかというと次のようなケース
char foo[] = "abcd"; char * const baz = foo; // ① const char * bar = foo; // ②
この辺は分かっている人も多いと思いますが、
①はポインタ変数に対する代入が禁止になります。baz = bar; が禁止
②はポインタの指し示す先のchar型の変数に対する代入が禁止になります。bar[0] = 'e';が禁止
となります。天下り的に覚えていても問題ないのですが、忘れがちなのではないかと思います。では次のようなケースはどの代入が禁止になるでしょうか?
char foo[] = "abcd"; char bar[] = "efga"; char *baz = foo; char *qux = foo; char * const * foobar = &baz; foobar = &qux; // これが禁止?① *foobar = bar; // これが禁止?② *foobar[0] = 'h';// これが禁止?③
答えは②ですが、前出のケースを天下り的に覚えた場合はこのケースに対応するのが難しいんじゃないでしょうか? これから紹介する二つのルールを覚える事でconstの位置を正しく選ぶことが出来るようになります。
1) const は 「① const ② ③ ④ ‥」という順序で修飾する型情報を探索し、変数名のTokenにたどりつくと探索をやめる。
※ データ型、および間接演算子”*”の場合に探索を続ける。
※ const等の修飾子は無視する。
2) constが修飾できる型は以下の三つに分類される。
\(データ型\) | 任意のデータ型の変数に対する代入を禁止する |
\(データ型 *^i\) | ポインタの指し示す先の任意のデータ型の変数に対する代入を禁止する |
\(*^i\) | ポインタ変数に対する代入を禁止する |
※間接演算子"*" が二個以上ある場合は”ポインタの指し示す先”を付け加える事になります。たとえば** であればポインタの指し示す先のポインタ変数への代入が禁止になります。
以上のルールを踏まえて先ほどのコードを見てみましょう char * const * foobar; ですがconst の修飾先の優先度①には* があります、さらに探索を続けると優先度②にも*があります。優先度③は変数名のTokenなので探索が中止されます。という事でこのconst が修飾するのは ** となり ポインタの指し示す先のポインタ変数への代入が禁止、すなわち *foobar = bar;が禁止になります。以上を踏まえてまとめると
constの位置 | 修飾する型 | foobar = &qux; | *foobar = bar; | *foobar[0] = 'h'; |
char ** const foobar | * | × | ○ | ○ |
char * const * foobar | ** | ○ | × | ○ |
const char ** foobar (=char const ** foobar) |
char** | ○ | ○ | × |
なおダブルポインタで全ての代入を禁止するには const char * const * const foobar = &baz; となります。例えば const char const * const * foobar = &baz; 等とやってしまうとそれは、同じ意味を持つconstが存在することになりwarningにはなりますがerrorにはなりません。しかもこの場合は foobar = &qux; は代入OKとなるのでconstの運用はしっかり理解して行いましょう。