15
14

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

const =定数(読み込み専用)と思い込むのはもうやめよう

Last updated at Posted at 2020-06-26

「const修飾子がついた値は定数となり、同じ関数内では上書きできない」と単純に思い込んでいたら思わぬところでハマったので。

constとは結局何なのか?

constの意味は、次のように定義づけられることが多い。

  • 定数(上書き不可)
  • 読み込み専用とする

この定義は、半分正解であり半分間違っている

 
constが定数として常に上書き不可を保証するのは、修飾する対象が非ポインタ型のときだ。
しかしポインタが絡むと、constが何を修飾しようとするか、constをどこに配置するかなど、細かな使い方によって挙動が変わるのである。

C言語を扱ううえでこの辺が今までふんわりしていたので、調べてわかったことを備忘録として残しておく。

const × 非ポインタ型の使い方

例えば下のようにchar型にconstをつけた場合、上書きしようとするとコンパイルエラーになる。

main.c
int main(void)
{
    char c        = 'A';
    const char cc = 'B';

    c  = 'Z'; // 上書き可
    cc = 'Z'; // 上書き不可。コンパイルエラーになる
    return (0);
}

 
このようにchar型やint型などにconstをつけると、常に上書き不可になる。

なお、この場合constをつける位置は重要ではない。
つまりconst intと書いてもint constと書いても挙動は同じである。

main.c
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より左の内容が上書き不可になるイメージ。
わかりにくいので具体例をあげると、

func.c
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の違い

15
14
6

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
15
14

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?