概要
CERN ROOT Jupyter notebookはインタプリタなので、簡単にいろいろなことを試すことができます。
今回はポインタ変数におけるconst修飾子について試します。
環境構築
関連記事
実行環境
root --version
ROOT Version: 6.32.08
Built for macosxarm64 on Nov 14 2024, 09:27:26
From tags/6-32-08@6-32-08
root-config --python-version
3.13.0
python --version
Python 3.13.0
Jupyter notebookでの実行内容の記載について
実行内容は本来ブラウザのスクリーンショットを載せるのですが、ちょっと煩雑なので次のように記載します。
入力セルの内容はC言語のコードブロックでファイル名をinput [番号]
出力セルは言語指定なしのコードブロックでファイル名をoutput [inputと同じ番号]
constの実験
ポインタもconstと一緒に使われることがある。constはバグを減らすためにも必要なことである。
constの位置は基本的に変数の直前にconstを書く。ただし、ポインタ型ではない基本型のconstは先頭に記述することとします。
const int num1 = 123; //一般的にはこっちなので今後はこっちを使います
int const num2 = 123; //基本はこっちのような気がする
int * const pnum1 = &num1; //ポインタの場合、pnum1を変更できないようにするには
//変数の直前にしかconstが書けないと思う
次の2行目の文でconstはどこに書けるでしょうか。そして、その変数に対して代入可能、代入不可能なのはどれかを実験します。const 0個も含める。
int num = 123;
int *pnum = # //constはどこに書けるか?
書き方は次の4つかなと思います。
int num = 123;
int *pnum1 = #
const int *pnum2 = #
int * const pnum3 = #
const int * const pnum4 = #
エラーが無いため出力なし
コンパイラによってはエラーや警告が出る可能性があるので、実際使うときは確認してください。
各変数に代入が可能なのか不可能なのかを確かめてみます。アドレスは0、intには1を代入する文にしました。
pnum1 = 0;
*pnum1 = 1;
pnum2 = 0;
*pnum2 = 1;
pnum3 = 0;
*pnum3 = 1;
pnum4 = 0;
*pnum4 = 1;
input_line_63:5:8: error: read-only variable is not assignable
*pnum2 = 1;
~~~~~~ ^
input_line_63:6:7: error: cannot assign to variable 'pnum3' with const-qualified type 'int *const'
pnum3 = 0;
~~~~~ ^
input_line_62:5:13: note: variable 'pnum3' declared const here
int * const pnum3 = #
~~~~~~~~~~~~^~~~~~~~~~~~
input_line_63:8:7: error: cannot assign to variable 'pnum4' with const-qualified type 'const int *const'
pnum4 = 0;
~~~~~ ^
input_line_62:6:19: note: variable 'pnum4' declared const here
const int * const pnum4 = #
~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~
input_line_63:9:8: error: read-only variable is not assignable
*pnum4 = 1;
~~~~~~ ^
エラーの原因
- pnum2は先頭にconstがあるので参照先の値を変更できない
- pnum3は直前にconstがあるのでpnum3自身を変更できない
- pnum4は先頭にも直前にもconstがあるので参照先の変更もできないし、pnum4自身の変更もできない
コンパイルエラーのない状態で実行しないように!
ポインタに0を代入しているので、アドレス0はアクセス違反になります。
私の場合はkernelが自動的に再起動されました。
今度は1行目にconstをつけて上記と同様の実験をします。
const int num = 123;
int * pnum = #
今回も4通りの文で試します。
const int num = 123;
int *pnum1 = #
const int *pnum2 = #
int * const pnum3 = #
const int * const pnum4 = #
input_line_64:3:6: error: cannot initialize a variable of type 'int *' with an rvalue of type 'const int *'
int *pnum1 = #
^ ~~~~
input_line_64:5:13: error: cannot initialize a variable of type 'int *const' with an rvalue of type 'const int *'
int * const pnum3 = #
^ ~~~~
エラーの原因
- pnum1の参照先のnumがconstで変更不可なのにはpnum1は参照先を変更可能として定義している
- pnum3もpnum1のエラーと同じ原因
変数宣言のエラー箇所を除いて、変数に代入をしてみます。
const int num = 123;
const int *pnum2 = #
const int * const pnum4 = #
pnum2 = 0;
*pnum2 = 1;
pnum4 = 0;
*pnum4 = 1;
input_line_65:7:8: error: read-only variable is not assignable
*pnum2 = 1;
~~~~~~ ^
input_line_65:8:7: error: cannot assign to variable 'pnum4' with const-qualified type 'const int *const'
pnum4 = 0;
~~~~~ ^
input_line_65:4:19: note: variable 'pnum4' declared const here
const int * const pnum4 = #
~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~
input_line_65:9:8: error: read-only variable is not assignable
*pnum4 = 1;
~~~~~~ ^
ちなみにinput [3]
をMacのgccでコンパイルしてみました。
#include <stdio.h>
int main() {
const int num = 123;
int *pnum1 = #
const int *pnum2 = #
int * const pnum3 = #
const int * const pnum4 = #
*pnum1 = 1;
printf("num = %d, *pnum1 = %d\n", num, *pnum1);
*pnum3 = 1;
printf("num = %d, *pnum3 = %d\n", num, *pnum3);
return 0;
}
> gcc test1.c
test1.c:4:7: warning: initializing 'int *' with an expression of type 'const int *' discards qualifiers [-Wincompatible-pointer-types-discards-qualifiers]
4 | int *pnum1 = #
| ^ ~~~~
test1.c:6:14: warning: initializing 'int *const' with an expression of type 'const int *' discards qualifiers [-Wincompatible-pointer-types-discards-qualifiers]
6 | int * const pnum3 = #
| ^ ~~~~
2 warnings generated.
> ./a.out
num = 123, *pnum1 = 1
num = 123, *pnum3 = 1
警告は出てますが、バイナリ(a.out)が出来て実行出来ました。しかし、numが変更されていないようになっています。理由は*pnum1=1
がメモリに反映されていないことにが原因だと思います。よって、すべての宣言にvolatileキーワードを追加してコンパイラの最適化による問題をなくして、試してみてください。 そもそもnumがconstでなければ問題はなかったはず。
constで定義した変数をポインタを使って強制的に変更可能にしない方が良い
最後にもっと*の数を増やして考えて見ましょう。
次のpnum2のポインタ変数にはどの位置にconstがつけられて、どの代入がOKか試す。
int num = 123;
int * const pnum1 = #
int * const *pnum2 = &pnum1;
int ***pnum301 = &pnum2;
int *** constpnum302 = &pnum2;
int ** const *pnum303 = &pnum2;
int ** const * const pnum304 = &pnum2;
int * const **pnum305 = &pnum2;
int * const ** const pnum306 = &pnum2;
int * const * const *pnum307 = &pnum2;
int * const * const * const pnum308 = &pnum2;
const int ***pnum309 = &pnum2;
const int *** constpnum310 = &pnum2;
const int ** const *pnum311 = &pnum2;
const int ** const * const pnum312 = &pnum2;
const int * const **pnum313 = &pnum2;
const int * const ** const pnum314 = &pnum2;
const int * const * const *pnum315 = &pnum2;
const int * const * const * const pnum316 = &pnum2;
constが書ける位置は4箇所あるので $2^4=16$ 通りあるので、pnum301からpnum316の16通り書くことが出来ます。結果はご自分で実行してください。また、gccなどのコンパイラを使って確認することもおすすめします。
次に代入の実験ですが、数が多いので代入文を生成するプログラムを作って、出力をコピペして実行します。下記のvalsの初期値を必要な番号だけ残して実行してください。
int vals[] = {1,2,3,4,5,7,8,9,10,11,12,13,14,15,16};
for(int i = 0; i < sizeof(vals)/sizeof(int); i++) {
printf("***pnum3%02d = 1;\n", vals[i]);
printf("**pnum3%02d = 0;\n", vals[i]);
printf("*pnum3%02d = 0;\n", vals[i]);
printf("pnum3%02d = 0;\n", vals[i]);
}
valsに1だけ残すと次のように出力されます。
***pnum301 = 1;
**pnum301 = 0;
*pnum301 = 0;
pnum301 = 0;
基本的にはポインタを定義するときはポインタ先の定義そのままに*を追加して、後は新しく定義した変数をconstにするかどうかだけです。強制的に上位にconst指定を変更しないほうが良いです。
つまり
型X xxx ~~~~;
型X *pxxx = &xxx;
or
型X * const pxxx = &xxx;
のどちらかで良いと思います
型X が const int * const
ならば
const int * const xxx = &yyy;
const int * const * pxxx = &xxx;
or
const int * const * const pxxx = &xxx;
終わりに
constは指定できるならバグを減らすという点では指定したほうが良いのですが、どうも面倒がって定数の値のとき以外はあまり指定しないことが多々ありました。 今は、定数の値のときにはconstexprを使っているのでconstを使うのがますます減っているかもしれません。