【C】constポインタは何が上書き不可なのか

2018-08-14

C言語では型にconstをつけることで、誤って値を代入しようとした場合にコンパイルエラーを発生させることができるが、 ポインタの場合constを置く位置によって変数自体かポインタの指す先かを制御できる。

非ポインタ型の場合

const int i = 1;
//i = 123; // エラー:再代入不可

int const j = 2; // 内容的には変数iと同じ
//j = 123; // エラー:再代入不可

ポインタじゃない型の場合には変数の値を定数として扱い、再代入をコンパイラがエラーにして防いでくれる。 const intでもint constの順でも意味は同じ。

単独のポインタの場合

ポインタが絡むとconstを置く位置によって何の上書きが禁止になるかが異なる:

const char* s11 = "foo";
//*s11 = 'x'; // エラー:ポインタの指す先の書き換え不可
s11 = "bar"; // 成功:変数の再代入は可能

char const* s12 = "foo"; // 内容的には変数s11と内容は同じ
//*s12 = 'x'; // エラー:ポインタの指す先の書き換え不可
s12 = "bar"; // 成功:変数の再代入は可能

char* const s2 = "foo";
*s2 = 'x'; // 成功:ポインタの指す先の書き換え可能
//s2 = "bar"; // エラー:変数の再代入不可

const char* const s3 = "foo";
//*s3 = 'x'; // エラー:ポインタの指す先の書き換え不可
//s3 = "bar"; // エラー:変数の再代入不可
  • 文字列だとconstを置く位置はアスタリスクの前後の2通りがある。
  • 前:s11はポインタが指す中身が書き換えられないことを保証する。 ポインタ変数の値自体は変更できる。
    • s12s11と同じ。
  • 後:s2は逆にポインタが指す中身を書き換えられるが、ポインタ変数自体は再代入不可。 (ただし上の例だとs2が指している文字列"foo"自体は上書き不可能なデータなので、コンパイルは通るが実行すると強制終了する。)
  • 前後:s3が完全な定数となる。

二重ポインタの場合

例えばC言語では文字列が char へのポインタなので、文字列の配列は二重ポインタ char** になる。 それに対する const を置ける位置は3ヶ所なので、組み合わせは2の3乗で8通り:

void func1(const char** buf1) {
//*buf1[0] = '\0'; // エラー:文字列中の文字の書き換え不可
buf1[0] = NULL; // 成功:配列の内容の書き換え可能
buf1 = NULL; // 成功:ポインタの指す先を変更可能
}

void func2(char* const* buf2) {
*buf2[0] = '\0'; // 成功:文字列中の文字の書き換え可能
//buf2[0] = NULL; // エラー:配列の内容の書き換え不可
buf2 = NULL; // 成功:ポインタの指す先を変更可能
}

void func3(const char* const* buf3) {
//*buf3[0] = '\0'; // エラー:文字列中の文字の書き換え不可
//buf3[0] = NULL; // エラー:配列の内容の書き換え不可
buf3 = NULL; // 成功:ポインタの指す先を変更可能
}

void func4(char** const buf4) {
*buf4[0] = '\0'; // 成功:文字列中の文字の書き換え可能
buf4[0] = NULL; // 成功:配列の内容の書き換え可能
//buf4 = NULL; // エラー:ポインタの指す先を変更不可
}
  • func1buf1は文字列は上書き不可、配列とポインタは可
    • constcharを入れ替えた場合は同じなので省略
  • buf2は配列は上書き不可、文字列とポインタは可
  • buf3は文字列と配列は上書き不可、ポインタは可
  • buf4はポインタは上書き不可、文字列と配列は可
  • 以下[buf4]×[buf1,buf2,buf3]で組み合わせ

考え方

プリミティブな型の前でも後でも意味は変わらないので後に置くことにして(ex. char const)、 constより左の内容が上書き不可になる:

文字 文字列 文字列配列
char const** buf1 不可
char * const* buf2 不可
char const* const* buf3 不可 不可
char ** const buf4 不可

でもまあ実際は何個も書くのが面倒なので、「書き換え不可な文字列配列」を意図してても単にconst char**にしちゃうよねぇ…。

関数への引数時

定義側の規則はわかったのだが、関数に渡せる/渡せないの基準はよくわからなかった:

// 前の節の関数 func1~3 に対して、渡せる型は?

const char** buf1 = NULL; // 文字列の内容の書き換え不可、な文字列ポインタ
func1(buf1);
//func2(buf1); // warning
func3(buf1);

char* const* buf2 = NULL; // 配列の内容の書き換え不可、な文字列ポインタ
//func1(buf2); // warning
func2(buf2);
//func3(buf2); // warning

const char* const* buf3 = NULL; // 文字列および配列の内容の書き換え不可、な文字列ポインタ
//func1(buf3); // warning
//func2(buf3); // warning
func3(buf3);

char** buf0 = NULL; // 書き換え可、な文字列ポインタ
//func1(buf0); // warning
func2(buf0);
//func3(buf0); // warning
  • buf1は定数文字列の配列で、上書き可能な文字列を要求するfunc2へは渡せない、これは順当。
  • buf2は固定配列で、配列内容を書き換えるfunc1に渡せないのは妥当だけど、func3にも渡せない。
    • 渡せてもよさそうなに、なぜなのか!?
  • buf3は文字列と配列どちらも定数なので、当然func3にしか渡せない、順当。
  • buf0はすべてに渡せてよさそうなのに、結果的にbuf2と同じ。なぜだ…。

非constポインタ型からconstポインタ型へは暗黙で変換できるもんかと思うんだけど、そういうわけでもないっぽい。 どういうルールなんだろう?

  • Double pointer const-correctness warnings in C - Stack Overflow 読んでもよくわからなかった…
  • その下にCの仕様がおかしい、C++はできる、とのこと。 確かにg++で試してみるとbuf2は意図通りfunc3にも渡せるようになるが、buf0func1に渡せないのは変わらず。