いろいろと難しいが、とにかくまずは、アドレスを指定する時には二つのレジスタが必要と理解しよう。
なぜ二つ? 普通は一個でしょ??
まったくその通りである。通常のCPUはそういうのが多い。
・・・しかし、ちょっとここで仮定してみる。16ビットCPUである8086が仮にセグメントなんていうややこしい仕組みを持っていないとしよう。
よし! メモリを全部読んでみる実験だ! START: MOV BX,0x0000 ;開始する番地 MOV CX,0xFFFF ;ループする回数 JMP LOOP FORNEXT: MOV AX,[BX] ;BX番地のメモリの内容をAXに読んでみる ADD BX,0x0001 ;BXの番地をひとつ増やす LOOP FORNEXT ;CXの値をひとつ減らしてFORNEXT部にジャンプ
さぁこれで全部のメモリを読むことはできたよね?
・・・全部のメモリって、いくつ? ・・・・
0x0000番地〜0xFFFF番地まで。0xFFFFバイト。10進数に直すと65,535バイト。これを1024で割り算してKB単位にすると・・・・約64KB・・・えぇぇ!! たった64KB!???
そう!、セグメントなんてややこしいことを排除するとシンプルになるけど、16ビットでは扱えるメモリが猛烈に小さくなってしまうんです!!!
参考までに。
32ビット。16ビットのたった2倍だよね?。上記と同じように計算してみると・・・
0x00000000番地〜0xFFFFFFFF番地まで。0xFFFFFFFFバイト。10進数に直すと4,294,967,295バイト。これを1024で割り算してKB単位にすると・・・4,194,304KB・・・さらに1024で割り算してMB単位にすると・・・4096MB・・・もういいよね? なんと、4GB!!!
こんなに贅沢に沢山のメモリを使えるので、32ビットの時にはセグメントなんてない。というか、そんなややこしいことしなくてもいいわけ。(注1)
そう。CPUのビット数だけ見ると、たかだか倍なのでたいしたことないように見えるかもしれないけど、16ビットと32ビットじゃ、こんなに巨大な差があるんだ!
「じゃ、全部32ビットにすりゃいいじゃん!」と考える人がいたら多分その人はあまり頭がよくないと思うよ。
江戸時代の人に向かって「バカだなぁ。トラック使えばお城なんて簡単に作れるのに」と言っているのと同じだからね。
8086はたった64KBしか扱えないという制限を何とかしたかった。そのため、メモリ番地の指定だけは二つの16ビットレジスタを使うようにしてなんとか凌ごう!という方法を取ったわけ。
メモリの番地を指定する時だけは、 セグメントレジスタ & オフセットレジスタ という、二つのレジスタが使われるようにした。これがセグメントの考え方。
具体的には????
ここに、物理的にメモリを1MB搭載したマシンがあるとする。1MBということは、だいたい0x00000〜0xFFFFFまでメモリが搭載されていることになるよね?。
例えば、上記の、セグメントなんてない16ビットCPUの場合。番地を指定するのもレジスタだよね?。それが16ビットしかないんだから、0x0000〜0xFFFFまでの値しか指定できない。たとえ物理的に何GBメモリを搭載していても・・・。
;このマシンは1MB搭載機だ!0xFFFFFまでメモリが使えるぞ! ;さっそく一番最後のメモリを読んでみよう! MOV BX,0xFFFFF ;BXに読みたいメモリの番地を代入 MOV AX,[BX] ;BX番地のメモリの内容をAXに代入 ;わーい! これで1MBの所を読めたぞ!
これは大間違いでしょ? (^^;
BXは16ビットだもん。0xFFFFFなんて値、桁がないので代入できないよ!(よく見て。5桁あるでしょ?)
ここでセグメントレジスタの出番となるわけ。
上記の間違いプログラムと同じことをしたい場合こうなる。先に見てみて。
;このマシンは1MB搭載機だ!0xFFFFFまでメモリが使えるぞ! ;さっそく一番最後のメモリを読んでみよう! MOV DS,0xF000 ;セグメントレジスタの一つ、DSにセグメントを MOV BX,0xFFFF ;BXに読みたいメモリのオフセット値を MOV AX,[DS:BX] ;こんなことすると、0xFFFFF番地を読める! ;わーい! これで1MBの所を読めたぞ!
原理はややこしく書いてあることが多いけど、ようは一桁ずらすだけ。
MOV DS,0xF000 ;DSにセグメント値を入れる。 MOV BX,0xFFFF ;BXに読みたいメモリのオフセット値を DSの値 F000 ;←1桁左にずれる BXの値 + FFFF ↓ MOV AX,[DS:BX] ↓ MOV AX,0xFFFFF ;←こうしたのと同じことになる
備考
例えば。ここに1MBを搭載したPCがある。で、なんらかの方法で0x12345番地に0x10を書き込んだとする。
この場合、以下は同じ0x12345番地を読むことになる。
;---- コードA ------------ MOV DS,0x1000 ;セグメントレジスタであるDSに0x1000を代入 MOV BX,0x2345 ;BXレジスタにオフセット値0x2345を代入 MOV AX,[DS:BX] ;AXには物理的に0x12345番地の値(0x10)が入る
;---- コードB ------------ MOV DS,0x1230 ;セグメントレジスタであるDSに0x1230を代入 MOV BX,0x0045 ;BXレジスタにオフセット値0x0045を代入 MOV AX,[DS:BX] ;AXには物理的に0x12345番地の値(0x10)が入る
豆知識〜 枝豆ってや〜
このちょっと変則的な方法(二つの16ビットを組み合わせて指定する)。前の方の値をセグメントといい、後ろの値をオフセットと言う。
セグメントというのは英語で断片とか分割とかいうような意味。
さらにオフセットとは、ずれている量みたいな意味合いになる。
こういった名づけから、セグメント:オフセットは、以下のような説明がなされることが多い。
- セグメント値:メモリを分割して64KB単位で扱う場合の先頭アドレス(開始番地)
- オフセット値:セグメント値を基点(スタート位置)にして、そこから何バイト後ろにずれているか
そんなわけで・・・
8086、あるいはその互換であるリアルモード時のCPUは、ことアドレスを指定する場合は必ず2つで指定すると覚えておけばとりあえずは原理を理解できると思う。
アドレスを指定するっていうのはなにもプログラム・プログラマの話だけではないよ。
CPUが動作する場合に必要な部分でも全てこの法則に当てはめれば、別の形で整理できるんじゃないかな?
例えば・・・・
- プログラム上でのアドレス指定
- これまで説明してきたからわかるよね?
- インストラクションポインタ
- これはCPUが次の命令のアドレスとしているもの。
- なので、[CS:IP]という2つのレジスタが必要
- スタック
- CPUが一時的に使う変数とかを保持しておくための場所を示している。
- なので、[SS:SP]という2つのレジスタが必要
ウソつけ!32ビットでもセグメントあるじゃん!
・・・実はその通り。
・・・でも、これがややこしさに拍車をかけている要因。
16ビット時と32ビット時は、セグメントという言葉の意味合いがぜんぜん変わってしまうんだ!
ちょっとこの図を見てみて。ややこしいけど解るでしょ?「エロマンガ」という言葉についての図。
┌─ 日本人 ──── エロマンガ ──── 「うっしっし!」 │ 人類 ──┤ │ └─ フィリピン人 ─ エロマンガ ──── 「美しい島だ」
これと同じくらい、同じ言葉でもその意味合いが違うんですよぇね。
┌─ 16ビット時 ──── セグメント ──── これで1MBアクセスできる! │ x86 ──┤ │ └─ 32ビット時 ──── セグメント ──── マルチタスクでは必要だ!