プログラミング

プログラム初心者がC言語のポインタを考える1【Visual Studioのメモリウィンドウを使用した詳細なデバッグ】

早くも年末ですね~
ブログの更新おせーよ!!って自分でも思うわけですが、限りある時間を見つけてなんとか書いていかねば!

それはさておき、最近はアナログ回路から少し離れて、会社ではCPU周りのファームプログラムを学んでおります。
最近ではアナログ回路のみで完結する装置などはほぼ少なくなっており、制御するために様々なICを使います。
デジタル回路を用いてみたり、CPUでADコンバータやDAコンバータを制御したりするわけです。

その際に、PICやAVR、ARMなどのCPU内臓のICを用いて制御することがあるわけですが、装置の通信やICを直接制御するには、ハードウェアに近い言語を使わねばなりません。

その最たるものがアセンブラですが、「今の時代アセンブラなんて書いてられん!
無駄にコード行数が長くなるし、読みにくい!」という問題があるわけです。

そこで、C言語が挙げられるわけです。
しかして、こいつは最近流行のオブジェクト指向言語と少し趣が違う。

というのは、メモリを直接いじれたりするからであります。

つまり、C言語の文法の問題というより、CPUやメモリの原理を理解している等のハードウェアに関する知識が必要なのです。

C言語でデバイスのプログラムなどやったことなかった僕は、ほぼすべての関数でつまづき、倒れ、意味が分かりませんでした。

C言語の文法の問題だだと思っていたポインタに関しても、「は?なにやってんの・・・・俺の頭では・・・即座に理解ならん!!」となりました。
然る後、メモリを読め!という先輩の指示に従い、デバッグでメモリを見つつプログラミングをしていたら、なるほどどうして理解の速度が上がりました。
C言語の文法と思っていたポインタさえ、メモリというハードウェアの領域だったのです。

ということで、ここでは、難解であるC言語プログラムのポインタとやらをメモリウィンドウによるメモリの追跡をすることで、実体験的に把握しようと努めます。

なので、専門用語は本とかを見ると、載りまくっているので必要と思われる部分以外は使用しません。

C言語のポインタを考える

使用するソフトとプログラム

使用するソフトはVisual Studioで、あまねく学習者が無料で使う事が出来る高機能エディターです。

別にこのソフトでなくてもいいのですが、メモリも参照することが出来ますし、何より情報があふれているので、困った時にも調べやすいことと思われます。

以下に今回書いたコードを載せております。
至ってシンプルなコードで、numberに5を代入するとcubeByReference関数により、5*5*5 = 125となって値が出てくるというものです。

およそ、細かい点は知らなくても、結果が予測できるようなプログラムとなっております。

 

デバッグ

雰囲気こうなるんだろうなぁ、と思って写してコンパイルしてみても、さして理解したような気になれない不思議。

ここは、1行ずつデバッグして変数とそのメモリのアドレスを調べてみましょう。
ブレークポイントをint main()の最初の行(6行目)において、ステップイン(F11ボタンを押す)により、1行ずつデバッグすることが出来ます。

メモリウィンドウの使い方は以下のサイトに見やすく書かれていますので、参考にしてみてください。
Visual Studioのメモリウィンドウがこんなに使いやすいなんて知らなかった

6行目

6行目でデバッグすると、numberに初期値を入れていないため、10034707という全くランダムな数字が入っていることがわかります。
初期化の重要性がわかりますね。

9行目

次に、numberというint型の変数に5を代入します。
numberのメモリアドレスを見てみると、住所はどうやら0x00000005ということです。

本当かよ?この5って代入した値の5なんじゃねーの?と思って調べてみますと、変数を下に突っ込むとその中の値が出力される模様。
後にこれを検証します。

11行目

変数に&をつけることで、その変数のアドレスがわかります。
先のnumberのアドレスを確かめるべく、&numberを出力させてみます。

すると、結果は、0095FA5Cということが判明。もちろん値は5のままです。
ここで発見したのが、numberのみを見ると、アドレスの部分に05と出るのですが、&numberをメモリウィンドウに突っ込むと、以下のようになります。
先頭の05は16進数の0x05です。
16進数の0x05は10進数で5となるので、値はあっております。

アドレス:0x0095FA5C
値:05

この結果から、変数をメモリウィンドウに突っ込むとアドレスの部分に値が出て、下のメモリ番地の部分は????になるということが判明しました。

デバッグ16行目

l12行目に行くと、cubeByReference関数のあるl15に飛びます。

cubeByReference関数の引数は、&numberですのでnumber変数の番地をそのまま値として運ぶということになります。
ここらへんは、デバッグして初めて把握しましたね!!
メモリウィンドウみないままやってると、全くわからなかった。。。

さて、nPtrだけをメモリウィンドウに突っ込むと、以下のようになります。
つまりは、&numberで見たのと全く同じですね。

 

ついに*が出てきました。こいつがポインタというやつですね。
*は初心者殺しで、ざっとCの本を読んだりしても全然わからないわけです。
ということで、細かいことはさておき、どう動くのかを追跡してみましょう。

*nPtrをメモリウィンドウに突っ込むと、05と出てきます。
これは先に述べた通り、メモリ番地の表示が?になっておりますので、アドレスではなく値を示しています。

&をつけると、その変数のアドレスを取得することが出来ました。&number = 0x0095FA5Cだったわけです。
さらに、*をつけると、そのアドレス(0x0095FA5C)に入っている値(5)を取り出すことが出来るということです。

う~む、難しい!けど、メモリウィンドウを負い続ければ、把握は出来るわけです。

 

18行目

*nPtrを三乗したわけですが、赤くなっているところは値が変化した部分を示しています。
つまり、05から7dに値が変わったわけです。
16進数の7dは10進数で125なので、きちりと計算されていることがわかります。
重ね重ねになりますが、*をつけることで、アドレスの中身を取り出すことが出来るようです。
これがどうやらポインタの逆参照というやつらしい。

さて、ここで*nPtrが三乗されたわけだが、この計算によりnumberの中身の値が変わる、ということも観測されました。
これも、地味に難しいポイントだと思われます。

先の16進数の掛け算を電卓などを用いてやってみましょう。
(Windows10の電卓を使えば、16進数の計算も一瞬で出来ます)
電卓のプログラマー欄を選択した後、16進数なのでHEXを選択します。

普通に5*5*5をしますが、実際は

0x05 * 0x05 * 0x05 = 0x7D
となっており、正しいことが分かります。

まとめの図

ポインタの短いプログラムを実行して、メモリのアドレスを確認しつつ動きを確認しました。
その結果を再解釈し、絵にすると以下のようになるのではなかろうか?
まず、numberのアドレスが0x0095FA5Cであることは調べてわかっております。
このアドレスは、変数の頭に&を付けることで、取得できました。

まだ、こいつは理解しやすい。
問題は、*をつけたやつです。

nPtrの中にはnumberのアドレスが格納されていました。
このアドレスに保存されている値を取り出すのが、*nPtrの働きであったことは、先の実験から明らかになったことです。
*nPtr、すなわち逆参照という概念は、2つの働きを持っているというようにも見えます。

1.格納されている値のアドレスへ移動
2. 当該アドレスの値を取得

この複雑な動きのせいで、我々初学者を地獄に叩き落していると思われます。
 

 

さて、ここまで言語化して、ようやく自分でもおぼろげながら理解してきました。
次も、このポインタ関係をより深く追求していこうと思っております。