C言語を使用していて、最初に悩まされるのがポインタである。
C言語を学習してある程度は使えるようにはなってもポインタを真に理解するまでには時間が
かかる場合が多い。
ポインタについてのみ書かれている書籍が出版されているくらいである。
実行時にポインタの不正によってトラブルになるのもC言語が少しわかってきた頃である。
Java は、簡単に言えばこのポインタをC言語から完全に除去した言語であり、不正ポインタに
よるトラブルを無くすことを、ひとつの目的としている。
Java は 言わばC++の簡易版である。しかしポインタを真に理解し始めるとポインタの無い
言語はかなり不便なことに気づくことになる。
最近ではRPGでもポインタが使えるようになってきた。
一体、ポインタとは何であろうか?RPG開発者にも理解して欲しいポインタについて解説する。
最初にポインタとはメモリの番地を表すと書籍などには解説されてあり、その通りである。
しかし何故ポインタを使うのであろうか?
単なる変数だけでは駄目なのであろうか?
ポインタとは先にも言ったようにメモリ番地のみの定義である。
これに対して一般の変数はメモリ番地と、そこから割り振られる長さとがセットになって実態のある
変数として定義される。
例えば
char buff[256];
という定義は buffという名前のどこかのポインタ(番地)を起点として そこから 256バイトの領域を
変数の領域として割り当てることを意味する。
具体的に 番地 buff がどこの位置であるかを知る必要は無いし、システムが勝手に割り当てる
のである。buff[256] は変数であるが buffはポインタである。(重要!)
同じように
int num;
という定義は num という名前の番地から 4バイトを割り当てることを意味している。
これに対して
char* ptr;
という定義は ptr という名前の番地のみの定義(ポインタ)であり、長さの実態を持たない。
つまり簡単に言えば「変数名」のようなものである。
ptr = buff;
とすれば buff と ptr は 全く同じものとなる。
先の疑問に戻ってポインタを定義する理由は
- 長さがどのようなものになるか不明な変数を定義する。
- 変数名をポインタとして扱う
- 関数のパラメータを更新したい場合に使用する。
ことが代表的な目的となる。
次にそれらの具体的な内容について解説する。
1.長さがどのようなものになるか不明な変数を定義する
2.変数名をポインタとして扱う
例えばあるストリーム・ファイルを読み込んで変数にその値を入れたいとする。
このような作業をするためにデータを保管する変数は何バイトで定義すればよいであろうか?
1024バイトを用意すれば 2048バイト長のファイルは読み込むことはできない。
また1024バイトを用意して実際は24バイトしかなかったのであればかなりメモリの無駄になる。
きっちりファイルの大きさの分だけの変数を用意したいことになる。
つまり変数は定義するとしても、その長さはいくつになるかはわからないのである。
このようなときには単純な変数ではなく、ポインタを定義して、保管に必要な長さだけをファイルの大きさを調べてから定義すればよい。
このような柔軟な処理をできるのはポインタの定義ができるからである。
【 例: 任意の長さのストリーム・ファイルを読む 】
0001.00 #include <stdio.h> 0002.00 #include <stdlib.h> 0003.00 #include <string.h> 0004.00 0005.00 #define TRUE 0 0006.00 #define FALSE -1 0007.00 0008.00 void main(void){ 0009.00 int fildes; 0010.00 struct stat info; 0011.00 char dir[] = "/QATMHSTOUT/ERR404.HTM"; 0012.00 long len; 0013.00 char* buf; /* ポインタとして定義 */ 0014.00 0015.00 if((fildes = open(dir, O_RDONLY)) == FALSE){ 0016.00 printf("NOT FOUND %sn", dir); 0017.00 getchar(); 0018.00 return; 0019.00 } 0020.00 /*( ファイル・サイズを取得 )*/ 0021.00 if(lstat(dir, &info) == FALSE){ 0022.00 printf("CANNOT GET FILE SIZEn"); 0023.00 getchar(); 0024.00 return(FALSE); 0025.00 } 0026.00 len = (long)info.st_allocsize; 0027.00 /*( 読み取りバッファーを確保 )*/ 0028.00 buf = (unsigned char *)malloc(len); 0029.00 memset(buf, 0, sizeof(buf)); 0030.00 byte_red = read(fildes, buf, len); 0031.00 printf("READ OKn");/*DEBUG*/ 0032.00 getchar(); 0033.00 buf[byte_red] = 0x00; 0034.00 pos = 0; 0035.00 #pragma convert(850) 0036.00 ptr = strstr(buf, "T1.SHCODE"); 0037.00 #pragma convert(0) 0038.00 if(ptr != NULL){ 0039.00 pos = (int)(ptr - buf); 0040.00 printf("found SHCODE = %dn", pos);getchar(); 0041.00 } 0042.00 else{ 0043.00 printf("not found SHCODEn");getchar(); 0044.00 } 0045.00 getchar(); 0046.00 }
【 解説 】
変数 char* buf; としてポインタとして定義された buf は
buf = (unsigned char *)malloc(len);
によって長さも定義されることによって実体を持つようになる。
最後は free(buf) によってメモリを解放する。
このようにポインタとしてだけ変数を定義しておいて実行時に動的に変数の長さを
定義できる。
このような動的な変数の定義は便利であるばかりでなく必要不可欠であることが
ご理解頂けるであろう。もし、このような定義ができなければ自由度は制限されてしまう。
ストリーム・ファイルを扱うときは必須となる。
3.関数のパラメータを更新したい場合に使用する
C/400の中で定義される関数で戻り値が複数個必要な場合がある。
例えば RPGプログラムで別のRPGを呼んでパラメータを更新してから戻すと元の呼び出し側の
RPGでは渡したパラメータを戻り値として読み込むことができる。
またRPGの変数はすべてある意味ではグローバルとして定義されているのでサブ・ルーチンの中
で何か変数を更新すると親ルーチンに戻ってもその更新された変数を使用することができる。
ところが C/400では必ずしもそうではない。
次の例を見て頂こう。
0001.00 #include <stdio.hgt; 0002.00 #include <stdlib.hgt; 0003.00 #include <string.hgt; 0004.00 0005.00 #define TRUE 0 0006.00 #define FALSE -1 0007.00 int Func1(int su01); 0008.00 int Func2(int* su02); 0009.00 0010.00 void main(void){ 0011.00 int su1, su2; 0012.00 int result1, result2; 0013.00 0014.00 printf("** 関数の戻りのテスト ***n"); 0015.00 su1 = 1; 0016.00 result1 = Func1(su1); 0017.00 printf("Func1 の結果 su1 = %d, result1 = %dn", su1, result1); 0018.00 su1 = 2; 0019.00 result2 = Func2(&su2); 0020.00 printf("Func2 の結果 su2 = %d, result2 = %dn", su2, result2); 0021.00 getchar(); 0022.00 } 0023.00 int Func1(int su01) 0024.00 { 0025.00 su01 = 2; 0026.00 return su01; 0027.00 } 0028.00 int Func2(int* su02) 0029.00 { 0030.00 *su02 = 2; 0031.00 return *su02; 0032.00 }
【 解説 】
このサンプル・ソースでは Func1 と Func2 という2つの関数を呼び出してその結果を表示
するだけの処理である。
Func1 と Func2 のどちらも処理内容は同じであり、パラメータに数字= 1 を渡すが
関数内部ではこれを 2 に変更して戻す。
結果は次のように表示される。
** 関数の戻りのテスト *** Func1 の結果 su1 = 1, result1 = 2 Func2 の結果 su2 = 2, result2 = 2
どちらも su1 または su2 に 値=1 を入れて関数を実行しているのだが、戻り値はどちらも
result = 2 が正しく戻っているが変数としてパラメータを定義したFunc1の場合は内部で
su01 = 2; を実行しているのに関数処理後、元の親ルーチンではsu1 = 1 というように
パラメータとしての値は変更されていない。
これに対してポインタとして定義したFunc2 の場合は su2 = 2 というようにパラメータも
変更されている。
これはパラメータの呼び出し側と呼び出される側のパラメータ変数部分が同じものを指して
いるからである。
このように戻り値も取得したい場合はパラメータもポインタとして定義すべきである。
OS/400には数多くのAPIが用意されているが、その大半のパラメータ、特に戻り値は当然の
ことではあるがポインタとして定義されている。
まとめ
ポインタがいかに重要なものであるかがご理解いただけたことと思う。
C/400の中でポインタを理解して使いこなせるようになればVisualC++ などが
ずっとやさしく理解できるようになるであろう。