「const修飾子がついた値は定数となり、同じ関数内では上書きできない」と単純に思い込んでいたら思わぬところでハマったので。
constとは結局何なのか?
constの意味は、次のように定義づけられることが多い。
- 定数(上書き不可)
- 読み込み専用とする
この定義は、半分正解であり半分間違っている。
constが定数として常に上書き不可を保証するのは、修飾する対象が非ポインタ型のときだ。
しかしポインタが絡むと、constが何を修飾しようとするか、constをどこに配置するかなど、細かな使い方によって挙動が変わるのである。
C言語を扱ううえでこの辺が今までふんわりしていたので、調べてわかったことを備忘録として残しておく。
const × 非ポインタ型の使い方
例えば下のようにchar型にconstをつけた場合、上書きしようとするとコンパイルエラーになる。
int main(void)
{
char c = 'A';
const char cc = 'B';
c = 'Z'; // 上書き可
cc = 'Z'; // 上書き不可。コンパイルエラーになる
return (0);
}
このようにchar型やint型などにconstをつけると、常に上書き不可になる。
なお、この場合constをつける位置は重要ではない。
つまりconst intと書いてもint constと書いても挙動は同じである。
int main(void)
{
char const c = 'A';
const char cc = 'B';
c = 'Z'; // 上書き不可。コンパイルエラーになる
cc = 'Z'; // 上書き不可。コンパイルエラーになる
return (0);
}
非ポインタ型にconstをつけるのは簡単だ。
ややこしいのは、ポインタ型にconstを修飾するときだ。
ポインタ型にconstをつける場合は、どこにconstを配置するかによって挙動が変わるので注意が必要である。
const × ポインタ型の使い方
例えば、次の4パターンの挙動の違いを正確に説明できるだろうか。
- const char*
- char* const
- const char* const
- char const* const
表にまとめてみるとこうなる。
意味 | 値の変更 | ポインタの位置変更 | |
---|---|---|---|
const char* | const char(読み取り専用)の文字列 | ❌ | ⭕️ |
char* const | char型のポインタをconst(位置制御)する | ⭕️ | ❌ |
const char* const | const char(読み取り専用)の文字列をconst(位置制御)する | ❌ | ❌ |
char const* const | char型をconst(制御=読み取り専用)してconst(位置制御)する | ❌ | ❌ |
覚え方としては、*(アスタリスク)は「その左側すべてに係る」と考えるのが一番わかりやすい。
- const char* → const char(読み取り専用)文字列
- char* const → char*(文字列)をconstする
[追記] 2020.6.28
コメントで多くのアドバイスいただきありがとうございます!
特に@yuki12さんの記事が大変わかりやすかったので追記いたします。
ポインタ型に付ける const は型名の後ろに付くと考えると憶えやすい
const × 二重ポインタ型の使い方
二重ポインタ型にconstを掛け合わせるとどうなるのか。
例えば次の4パターンはすべて挙動が違う。
- const char**
- char* const*
- const char* const*
- char** const
なんか頭が痛くなってくる...
二重ポインタの場合は、普通のポインタ型に比べて条件がひとつ増える。
それは「配列内容を操作できるかどうか」だ。
つまり、具体的な値を上書きするかどうかではなく、配列自体の順番を入れ替えたり、NULLポインタを指すよう上書きしたりする操作をここでは「配列内容の変更」と呼ぶことにする。
さて、上の4パターンの挙動を整理すると次のようになる。
意味 | 値の変更 | 配列内容の変更 | ポインタの位置変更 | |
---|---|---|---|---|
const char** | 読み取り専用の文字列(const char*)の配列 | ❌ | ⭕️ | ⭕️ |
char* const* | 書き換え可能な文字列(char*)をconst(配列内容を読み取り専用とする)する | ⭕️ | ❌ | ⭕️ |
const char* const | 読み取り専用の文字列(const char*)をconst(配列内容を読み取り専用とする)する。 | ❌ | ❌ | ⭕️ |
char** const | 書き換え可能な文字列の配列(char**)をconst(位置制御)する。 | ⭕️ | ⭕️ | ❌ |
覚え方としては、constより左の内容が上書き不可になるイメージ。
わかりにくいので具体例をあげると、
void func(const char **str)
{
str[2] = "aaa"; //値の変更:不可
str[0] = NULL; //配列内容の変更:可
str = NULL; //ポインタの位置変更:可
}
こんな感じ。
constを使いこなすメリット
プログラムによって細かく挙動を制御することで、思いがけないデータの変更を防ぐことができる。
チーム開発するときには使いこなせると大変便利。
特に大規模なプログラムになるほどconstの役割は重要になりそう。
またC標準ライブラリなどでもconstは頻繁に使われているので、自分では積極的に書かないまでも、せめて遭遇したときに正確に読めるようにしておきたい。
参考
【C言語入門】constの使い方
constポインタは何が上書き不可なのか
constとは
hiramine.com
const chars, char const sの違い