simotin13's message

simotin13といいます。記事の内容でご質問やご意見がありましたらお気軽にコメントしてください\^o^/

farポインタとポインタの違い

ポインタと__farのついたポインタのサイズの違いを意識する必要がありましたので書いておきたいと思います。

結論

__far付きのポインタと(__farのつかない)ポインタでは型のサイズが異なる場合があり、その場合アクセスできる領域も当然違ってきます。(ここでの __farはコンパイラでのキーワードです)

そもそも far とは何か?

16bit・32bit・64bitなどCPUのビット幅の違いとして、使用できるメモリの容量をよく耳にすることがあると思います。

パソコンだと、
「32bit版のWindowsだとメモリは4GBまでだけど、64bit版だと4GB以上メモリを搭載できる」

という話は皆さんご存知かと思います。

32bitで表現できる値は0~4294967295になりますのでアドレス空間に換算すると4GBの領域を表現できることになります。
64bitですと、0~18446744073709551616になり16EB(エクサバイト)まで表現可能です。
実際には16EBまでをアドレス空間として認識するOSはないと思いますが。

では、16bitではどうでしょうか?
16bitで表現できるアドレスは、0~65535 になりますので単純にアドレス空間に換算すると64KBになります。

しかしながら世の中に存在する16bitのCPUにはアクセスできるアドレス空間は1MBというCPUがよくあります。
有名どころですと、Intelの8086も1MBのアドレス空間を利用可能です。マイコンだとルネサスのM16Cも16bitで1MBのメモリ空間にアクセス可能です。

1MBのアドレス空間が利用できると言っても、

16bitでどうやって64KBより大きなアドレスを表現するのか?64KBが上限じゃないのか?

という疑問が湧いてくると思います。

セグメントレジスタ

では、どのようにして16bitCPUが1MBのアドレス空間にアクセスしているかというと、M16CやRL78、Intel8086などの16ビットCPUにはセグメントレジスタというレジスタがあり、このレジスタを使って64KBより大きいアドレスにアクセスすることが可能になります。

具体的には、セグメントレジスタのうち4bitを上位アドレスとして使うことで64KBより大きいアドレスへのアクセスを可能にしています。

要するにアドレスを決めるためのレジスタを2個使うというのが、16bitCPUで64KBを超えるアドレスへのアクセスのからくりです。

farポインタとポインタでは変数のサイズが違う

一般的にはポインタ変数のサイズはCPUのレジスタのサイズ・バスの幅によって決まります。
ポインタサイズを確認するC言語のコードは以下のようなコードになります。

#include <stdio.h>

int main(int argc, char **argv) {
	int ptr_size;

	ptr_size = sizeof(int *);

	printf("ptr_size:%d\n", ptr_size);

	return 0;
}

32bit環境であれば、「ptr_size:4」と表示されますし、64bit環境であれば「ptr_size:8」と表示されます。

※64bit OS上でビルドしても、ビルドの設定が32bit用になっていれば「ptr_size:4」と表示されますので注意してください。

C言語からはレジスタに直接アクセスできない!

さて、セグメントレジスタを使うことで16bitでは表現しきれないアドレスの表現ができることは理解して頂けたかと思いますが、実際にC言語でそのようなコードを書く場合はどうなるのでしょうか?

例えば0X0FFFFFに 1234を書き込むコードは以下のようなコードになります。

    *((int *)(0X0FFFFF)) = 1234;
    

のようになります。

ところが、このコードは16bitCPUでポインタ型2byteの場合、警告がでて、そのままでは意図した結果になりません。

ポインタ型が2byteであればアクセス先は0xFFFFに丸められてしまいます。

0x0FFFFFにアクセスする場合、
セグメントレジスタには、0x000Fを設定してあげる必要があるのですが、C言語にはCPUのレジスタにアクセスする手段はありません。
※厳密にはインラインアセンブラの機能を持つコンパイラであれば可能ですが。

さて、ここでタイトルの通り、farポインタ(__farキーワード)の登場です。

RL78コンパイラで上記の例をfarポインタアクセスする場合、

    *((int __far *)(0X0FFFFF)) = 1234;

という書き方になります。

__farをつけることで、コンパイラに対し、

「これは64KBを超えるアドレスへアクセスしたいんだからね!アドレッシング気を付けてね!」
「ちゃんとセグメントレジスタを使ってね」

と要求することになります。

まとめ

現代のプログラマーは、普段プログラミングをしている限り、farポインタ について理解しておく必要はあまりないと思います。

しかし、組み込みソフトの開発で使われるマイコンには今でも16bit CPU/1MBアドレス空間のようなCPUがあります。
ただし、そのようなCPUであっても普通にプログラミングをしている場合はポインタのサイズやアクセス先がメモリ空間のどこに位置しているかを意識する必要はありません。

意識する必要が出てくるのは、上記のコードのようにアドレスを直接指定するようなコードを書くときぐらいでしょうか。