概要
CERN ROOT Jupyter notebookはインタプリタなので、簡単にいろいろなことを試すことができます。今回はポインタ(int *)等を試します。
環境構築は以下を参照してください。
実行環境
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と同じ番号]
実験
int a = 12345;
int *p1a = &a;
int **p2a = &p1a;
int ***p3a = &p2a;
&aは変数aのアドレスを表している。p1aはaのアドレスが格納されているポインタである。
アドレスとポインタの違いはアドレスは下記の0x137ff0000ようなメモリの特定の位置で、ポインタはアドレスが格納されているp1aのような変数である。
printf(" a = %d\np1a = %p\np2a = %p\np3a = %p\n", a, p1a, p2a, p3a);
a = 12345
p1a = 0x137ff0000
p2a = 0x137ff0008
p3a = 0x137ff0010
printf内の %pはアドレスを16進で表示する。出力結果の 0xは16進数であることを表す。
上記の結果をまとめると次のようになる
変数名 | 変数の型 | 変数の値 | 変数のアドレス |
---|---|---|---|
a | int | 12345 | 0x137ff0000 |
p1a | int* | 0x137ff0000 | 0x137ff0008 |
p2a | int** | 0x137ff0008 | 0x137ff0010 |
p3a | int*** | 0x137ff0010 | 未取得 |
p3aは((intのポインタ)のポインタ)のポインタである。
printf("*p1a = %d\n*p2a = %p\n*p3a = %p\n", *p1a, *p2a, *p3a);
printf("--------------------\n");
printf("**p2a = %d\n**p3a = %p\n", **p2a, **p3a);
printf("--------------------\n");
printf("***p3a = %d\n", ***p3a);
*p1a = 12345
*p2a = 0x137ff0000
*p3a = 0x137ff0008
--------------------
**p2a = 12345
**p3a = 0x137ff0000
--------------------
***p3a = 12345
上記結果をまとめるて表に*演算子を用いて値が等しくなったものを追加すると
変数名 | 変数の型 | 変数の値 | 変数のアドレス | 変数名と等しい |
---|---|---|---|---|
a | int | 12345 | 0x137ff0000 | *p1a, **p2a, ***p3a |
p1a | int* | 0x137ff0000 | 0x137ff0008 | *p2a, **p3a |
p2a | int** | 0x137ff0008 | 0x137ff0010 | *p3a |
p3a | int*** | 0x137ff0010 | - | - |
図(mermaid)で表すの次のようになる。2つ用意したが個人的には最初の図の方が中間のポインタを経由している感じが出て良いと思う。
ポインタ変数の前に*演算子を書くことでポインタ変数の値をアドレスとして、そのアドレスに格納されている値を表す。
**p3aは**(int***)p3aと考えれば*の数を引き算して相殺すると(int*)になり、***(int***)p3aは(int)になると考えることができる。
例えば*aや****p3aとすると、どうなるだろうか?
printf("*a = %x, ****p3a = %x\n", *a, ****p3a);
input_line_75:2:36: error: indirection requires pointer operand ('int' invalid)
printf("*a = %x, ****p3a = %x\n", *a, ****p3a);
^~
input_line_75:2:40: error: indirection requires pointer operand ('int' invalid)
printf("*a = %x, ****p3a = %x\n", *a, ****p3a);
^~~~~~~
当然ながらエラーになりました。
アドレス値を直接使った演算
printf("%x\n", *(int*)0x137ff0010);
printf("%p, %x\n", *(int**)0x137ff0010, **(int**)0x137ff0010);
printf("%p, %p, %d\n", *(int***)0x137ff0010, **(int***)0x137ff0010, ***(int***)0x137ff0010);
37ff0008
0x137ff0008, 37ff0000
0x137ff0008, 0x137ff0000, 12345
p3aの値0x137ff0010はアドレスであるが、0x137ff0010そのものは単なる数値でしかないので、どのように使うかは型を指定する(上記の場合はキャスト)ことによって決めることができる。
上記のoutputの1行目、2行目の37ff0008,37ff0000について、ポインタは8バイト(64ビットマシン)だがintは4バイトなので表示が4バイト分のみで途中で切れている。切れ方が変だと思う人は次回投稿予定の配列のところで説明するので確認してください。
また、*演算子は=(代入)の左側にも記述できます。
**p2a = 4321;
printf("a = %d\n", a);
a = 4321
***(int***)0x137ff0010 = 99999;
printf("a = %d\n", a);
a = 99999
上記2つの例はどちらも変数aを値を書き換えています。
今度は****(int****)0x137ff0010とは本来やってはいけないのですが実験なので試して見ます。
printf("%x\n", ****(int****)0x137ff0010);
outputが出ずフリーズしてしまいました。よってrestart kernelで対処しました。
再起動したので、いままで実行したものはすべて無効になりました。input [1]から再実行してもアドレスが変わっていることが多いので、アドレスを直接使うところはそもままでは使えません。
終わりに
私も覚えたての頃はポインタは難しかった。このポインタを使った間接参照がわかってからは、例えばEXCELでもできないかと思って探したらINDIRECT関数が見つかって重宝しました記憶があります。