本来ならここでアセンブリ的な知識を詳しく解説するところなのですが
初心者の方も見ている事を考慮してその「アセンブリ的知識」を要約して説明して行きます。
難しい説明→それを要約した説明
という形を取るので最後までお読みください。
まず、コンピュータは数字を記録する際に
電流のON(1)とOFF(0)で記録する
という事を最初に覚えて下さい。
でこの2種類の数字を使って色々な数字を表すのを2進数という訳ですが・・・
参考までに良く使われる2進数からあまり使われない8進数の対応表を以下に作って見ました。
(n進数を理解している人はこちらから表の下にスキップできます)
「10進数」というのは普段我々が利用している数字(○○円とかの○○の部分)で、
0-9までの10個の数字(正確には文字)が利用され「10カウントで桁上がりする(2桁になる)」ので10進数と言います。
10進数 |
2進数 |
8進数 |
16進数 |
0 |
0 |
0 |
0 |
1 |
1 |
1 |
1 |
2 |
10 |
2 |
2 |
3 |
11 |
3 |
3 |
4 |
100 |
4 |
4 |
5 |
101 |
5 |
5 |
6 |
110 |
6 |
6 |
7 |
111 |
7 |
7 |
8 |
1000 |
10 |
8 |
9 |
1001 |
11 |
9 |
10 |
1010 |
12 |
A |
11 |
1011 |
13 |
B |
12 |
1100 |
14 |
C |
13 |
1101 |
15 |
D |
14 |
1110 |
16 |
E |
15 |
1111 |
17 |
F |
16 |
10000 |
20 |
10 |
17 |
10001 |
21 |
11 |
18 |
10010 |
22 |
12 |
19 |
10011 |
23 |
13 |
20 |
10100 |
24 |
14 |
21 |
10101 |
25 |
15 |
22 |
10110 |
26 |
16 |
23 |
10111 |
27 |
17 |
24 |
11000 |
30 |
18 |
25 |
11001 |
31 |
19 |
26 |
11010 |
32 |
1A |
27 |
11011 |
33 |
1B |
28 |
11100 |
34 |
1C |
29 |
11101 |
35 |
1D |
30 |
11110 |
36 |
1E |
31 |
11111 |
37 |
1F |
32 |
100000 |
38 |
20 |
33 |
100001 |
39 |
21 |
34 |
100010 |
40 |
22 |
35 |
100011 |
41 |
23 |
36 |
100100 |
42 |
24 |
といった具合です。
で、ですね。
コンピュータは人間に取っては簡単な「1+1」の
「1」という数値を記憶するのにもメモリを利用します。
(正確には超高速なCPUのレジスタ(=計算機)内で記憶されている場合もありますが便宜上全てメモリで記憶するものとして説明します。)
で、メモリのどの位置に記録したかを特定するために
コンピュータ上では数字のみの番地
を用います。(「番地」の事を「アドレス」と言います。「番地」の英語訳のままですね。)
数字のみの番地では人間が覚える事が困難なため、
「変数」として名前を付けて宣言をすると勝手に番地が割り振られ、
プログラムをコンピュータに理解可能な形に翻訳する(コンパイル)際に
変数名→番地
という変換が勝手に行われています。
こうする事で人間に「コンピュータのメモリ上でのデータの出し入れ」を容易にしたのが「変数」と言えます。
さて、その番地なのですが、参考までに・・・
32ビットCPUと32ビットOS(殆どのWindowsXPと32ビット版Vista)では
32ビット分(=2進数32桁分)=00000000000000000000000000000000-11111111111111111111111111111111=0x00000000-0xFFFFFFFF
という番地となっています。
この「番地」という考え方は後で利用するのでここまでで理解できない人のために
番地=家の住所
と置き換えた文章も併記します。
で、件の画像。
とりあえずこれを分かりやすく枠づけしたのが↓
で、これを分解して説明して行きます。
まず1つめ。
これなんですが、
赤枠に緑枠を重ねて変数の色を変えたのには理由があります。
それは、この場合、Test1のaは「Dim
a」は別物扱いになるからなのです。
ByValが無かった時は
Result=Test1(a)が行われ、
a=a+10
が行われると、Dim
a,Resultのaに数値が加算されました。
ですから次のmsgboxのaの値は初期値である0に10が加えられ、10が表示されていました。
ところが、引数名(変数名)にByValを付けると、全体としてのaではなくTest1のaとして扱われ、
別物扱いとなります。
これで例えば、
Dim
a
a=30
Result=Test(a)
msgbox(a)
Function
Test(a)
a=a+10
Function
と書いた場合、
msgboxで「40」が表示されるんじゃね?
と思った方も居るかも知れませんが、
Test1が呼び出された時点で、
「全体としてのa」は「30」なので
「Test1のa」には「30」が渡(代入)されます。
そして「a=a+10」ですが、
aは「ByValが付いているので(「値渡し」と言います。)」
「Test1のa」でしかなく、ここでは「Test1のa」に「10」を加えてます。
(「全体としてのa」は30のまま)
Functionが終わる時、
「Test1のa」は「40」ですが、
Functionが終わるので「Test1のa」は消滅し、同時に「40」という値も消滅します。
で、その後
msgbox(a)
ですが、
ここでのaは「全体としてのa」でありその値は(Functionを呼び出す前の)「30のまま」です。
よって、
msgboxは「30」が出力されます。
これは非常に厄介なのですが、先程の「番地」という考え方を利用すると
視覚的に分かりやすいので
次の問題を説明し終わったあと解説します。
で、問題はこの次。
俗に言う「ポインタ」「参照渡し」と言われる概念。
さっきの画像を色を変えてちょっと手入れしてみたのが↓
「これは流石にaは20のままだろ。」と思ったそこの貴方。
残念ですがプログラムはそんなに甘くはないのです。
※逆に「いやむしろ裏をかいて30になるんだろ?」と思った人は、
いい根性していると自分は思います。ですが、仕組みが分かっていなかったら意味はありません・・・
実際に実行すれば分かるんですが、
aは30になっちゃいます。
何故かですが、ここで番地が登場します。
さてまず、
Option
Explicit
Dim
a,Result
の「a」を仮に「0x0番地」
(メモリアドレスは必ずこう書きます。「0x」に意味はないので住所で言うなら
「0番地(コンピュータの数字は常に0から始まるので住所と比べるとおかしいですが。)」です。)
とします。(Resultは説明する必要がないので番地を割り当てません。)
次に関数ですが、
(「...」は省略ですが、入る文字列は何でも構いません。)
Function
Test(...)
Test=10
End
Function
とやった場合、
Resutl=Test(...)
は
Result=10
に置き換わるので
「Test」という関数名は「変数」のような扱いを受けている事が分かります。
ですが、関数名は使わないので、関数の変数にのみ番地を割り振ります。
よって、
Function
Test1(a)
End Function
Function Test2(ByVal a)
End
Function
Function Test3(ByVal b)
End Function
Function
Test4(b)
End
Function
という4つの関数があるとして、<
br>
Test1のa=0x1
Test2のa=0x2
Test3のb=0x3
Test4のb=0x4
番地とそれぞれします。
そして
Option
Explicit
Dim a,Result
a=20
Result = Test1(a)
msgbox(a)
Result =
Test2(a)
msgbox(a)
Result = Test3(a)
msgbox(a)
Result =
Test4(a)
msgbox(a)
Function Test1(a)
a=a+1
End
Function
Function Test2(ByVal a)
a=a+2
End
Function
Function Test3(ByVal b)
a=a+3
b=b+3
End
Function
Function Test4(b)
a=a+3
b=b+3
End
Function
というプログラムを考えます。
で、このプログラムを書き換えて見ます。
(見づらいですが、視覚的に分かりやすくするためのものでしかありません。)
Option
Explicit
Dim 0x0,Result
0x0=20
Result =
Test1(0x0)
msgbox(0x0)
Result = Test2(0x0)
msgbox(0x0)
Result =
Test3(0x0)
msgbox(0x0)
Result = Test4(0x0)
msgbox(0x0)
Function
Test1(0x1)
0x2=0x2+1
0x1=0x2
End Function
Function Test2(ByVal
0x2)
0x4=0x4+2
0x3=0x4
End Function
Function Test3(ByVal
0x3)
0x0=0x0+3
0x6=0x6+3
End Function
Function
Test4(0x4)
0x0=0x0+3
0x8=0x8+3
End
Function
となるのですが、注目したいのはこれ、
Dim a
Function Test1(a)
a=a+1
End
Function
が
「全体としてのa」と「Test1としてのa」は同じとみなされる
と前で解説したにも関わらず、aの部分が、
Dim
0x0
Function Test1(0x2)
0x2=0x2+1
End Function
と、
「全体としてのa」の宣言部分での番地と「Test1内でのa」としての番地が異なっている
という事です。
このままでは本当に何が何だか理解できないと思うので、1つづつ分解して解説していきます。
まず、
Dim a
a=
20
Result=Test1(a)
Function Test1(a)
a=a+1
End
Function
なのですが、
Result=Test1(a)
でTestが呼び出された時、Test1の「aを渡す変数(「Test1(a)」の「a」)」には「ByVal」が加えられて居ません。
この場合、「Test1のa」には「全体としてのa」の「記録されている番地」が教えられます。
どういう事か?
aは0x0番地、
a=20
の時点で0x0番地は20ですね。
Result
= Test1(a)
で、Test1が呼び出される訳ですが「Test1のa」の値は「0x1番地」に記録されるため、
「全体としてのa」とは別の変数という事になります。
ですが、「ByVal」指定がない場合、「Test1のa」には「全体としてのa」の番地が教えられます。
ですので、
「0x1番地」の中身は「0x0番地」、つまり、
0x0=0x1
という事は、「全体としてのa」は「Test1のa」と等しいですよ
と言う事です。住所で例えるなら、
「名古屋市中区○○(0x0番地)」という住所を東京の人に教える為に、
「東京都港区○○(0x1番地)」という住所の書かれたメモを渡し、
「東京都港区○○」の敷地に「名古屋市中区○○へ行け(0x1番地の中身(=0x0番地))」と書かれたメモを置いておく様なものです。
ですので、0x1番地である「Test1のa」に
a=a+1
を行うと言う事はすなわち、「全体としてのa」に
a=a+1
と行う事と等しく、Test1が終了して「Test1のa」が消滅しても「全体のa」が変更されているからaの値が変わってますよ
というのがC言語のポインタやVBSの「参照渡し」なのです。
逆に、
Dim
a
a=
20
Result=Test2(a)
Function
Test2(ByVal a)
a=a+1
End Function
の場合「Test2のa」には「全体としてのaの中身(=ここでは「20」)」が教えられるだけです。
a=20が終わった時点でaである0x0番地の数値は20。
Result=Test2(a)
で、Test2が呼び出され「Test2のa」は先程と同じように(今度は)0x2番地へと記録されます。
ここまでは先程と一緒なのですが「Test2のa」には「全体としてのaの中身」が教えられるため・・・
0x2=20(「全体としてのa」のTest2を呼び出した時点での値。)
そして「Test2のa」に
a=a+2
を行いますが、番地が教えられている訳ではないので普通に計算をしてしまいます。
a=20+2
a=22
ここでのaは「Test2のa」なので・・・
0x2=22
0x0には何もしていない事になるので
0x0=20
さて、ここでTest2が終わって0x2は消滅します。
なので残るのは・・・
0x0=20
0x0はaなので・・・
a=20
20のままですね。
これを住所で例えるのは非常に難しいのですが、
そうですね・・・
東京の本社(0x0番地)から、愛知の子会社(0x2番地)に対して「予算はこれだから会社(子会社)の改装頼むわ」と連絡が来るわけですが、
工事を頼むのは子会社であり、本社の方はお金(変数の中身)が増えも減りもしない
といった感じでしょうか。
少々無理矢理ですがすいません・・・。
で、これらは名前が変わっても適用されるので・・・
Dim
a
a=
20
Result=Test2(a)
Function Test4(b)
b=b+1
End Function
この時「Test4のb」は0x4番地。
「Test4のb」には「ByVal」が付いてないので教えられるのは「全体としてのa」の番地。
なので・・・
0x4=0x0
Function
Test1(a)
の場合と同じですね。
この「参照渡し」「値渡し」はかなり理解が難しいので時間をかけて理解しましょう。
ちなみに、
Function
Test(c)
は
Function Test(ByRef c)と等しく、ByRefは省略できる
という事になります。
※こういう事もできる。
Function
Test(ByVal a,b,ByVal c,ByVal d....)
「それぞれByValを指定するかどうか決める事ができる」
ので覚えておくと便利かも・・・?