C言語の最初の壁、ポインターです。これは、RPG言語仕様にないものですから特に理解が難しいと思います。まずは、ポインターとはこんなものかっていう感覚をつかむようにしてください。
ポインターの説明に入る前に、以下のRPGをみてください。
RPGプログラム POINT1R
E PTRA 5 5 1
E PTRB 5 1
I DS
I 1 5 PTRA
I 1 5 PTRB
C*
C MOVE 'X' PTRB,1
**
ABCD
|
1バイトの文字を5個持てる配列、PTRA、PTRBを定義し、それをデータストラクチャーの同じ桁のところに定義しています。
ここでは、PTRA,1の値が"A"ならばPTRB,1も"A"ですよね。
PTRB,1を"X"に変更するとPTRA,1も"X"に変わります。
なぜかっていうと、PTRAとPTRBは同じエリアをさしているからです
ポインターとは、何かをさしている物です
それでは、以下のCのプログラムを作成してください。
(ソースコードは、CCSID(65535)で作成し、コンパイルオプションは*PRINTとDEBUG
*ALLを指定して作成してください)
ポインターを使ったPGM POINT1
/*---------------------------------------------------------------*/
/* PROGRAM-ID : POINT1 */
/* REMARKS : ポインターを使ったPGM */
/* AUTHOR : Y.Ide */
/* DATE-WRITEN : 98/04/28 */
/* VERSION : 01.00 ORIGINAL */
/*---------------------------------------------------------------*/
/* C言語の最初の壁はポインターです。 */
/* これを使いこなせばマスターへの道はすぐそこです */
/* */
#include<stdio.h>
void main()
{
/*ポインターの定義*/
char *ptr_a = "ABCD";
printf("ptr_a = %s \n",ptr_a);
}
|
作成したら実行して見てください、エラー無く作成できたでしょうか?
char *ptr_a = "ABCD";この部分がポインターの定義です。
*は間接演算子です。*をつけて定義するとポインターになります。
ここでは、ポインターを定義すると同時にそのポインターが、"ABCD"という文字列をさすように定義しています。
こう少し詳しく見てみましょう。
STRDBG POINT1を実行してください。
ソースコードが表示されますね。
F10のステップ実行を押してください。
デバッグの準備ができましたので、PGM POINT1を実行してください
CALL POINT1
最初のステップで実行が止まりました。
POINT1実行結果イメージ
以下のデバッグコマンドを実行してください。
[コマンド]EVAL ptr_a
[結 果 ]ptr_a = SPP:FB76AE82AF001008
SPP:はスペースポインターであることを表します。
FB76AE82AF001008は、このポインターがさしている先のアドレスです。
(注:OS/400ではポインターを何種類かに分けて管理しています。スペースポインターは、自分のJOB/PGMで有効なメモリーエリアに対するポインターです。プログラムからのアクセスはポインターアドレスを使って自由にアクセスが可能ですが、他のJOBからは同じポインター値を指定してもアクセスすることはできません。何故かと言うと、スペースポインターとして使用できるエリアは、JOBごとに割り振られるからです(QTEMPもJOBごとで同じQTEMPでもJOBごと内容は違いますよね)AS/400のスペースポインターは、ユーザーから見えない部分に隠し情報を持っていて、JOBごとに管理されています。 他のプラットフォームとの実装の違いが一番でるのがこの部分で、UNIXなどからの移植で問題になりやすい部分です。この点については他の機会に詳しく解説したいと思います。)
次に、以下のコマンドを実行してください
[コマンド]EVAL &ptr_a
[結 果 ]&ptr_a = SPP:D3E8C9B064000320
&はアドレス演算子と言います。&をつけると変数自身のアドレスを帰します
&ptr_aはptr_a自身のアドレスを表します。
ptr_aはFB76AE82AF001008
&ptr_aはD3E8C9B064000320 です
次に以下のコマンドを実行してください
[コマンド]EVAL *ptr_a
[結 果 ]*ptr_a = 'A'
*は間接演算子でしたね、*とアドレスで、そのアドレスの値を戻します。
*ptr_aは(*FB76AE82AF001008)でそこには'A'の文字が格納されています
ポインターには、文字列”ABCD”の最初の'A'のアドレスしか格納してません。
Bにアクセスするにはどうすれば良いのでしょう?
BはAのアドレスの次にあるので以下のようにできます
[コマンド] EVAL *(ptr_a + 1)
[結 果 ]*(ptr_a + 1) = 'B'
このように、C言語では文字列の先頭のアドレスしか持っていません。
文字列の終わりはNULLが現れるまでという約束になっています。
PGMで文字列を使用するには、NULLで終端されているか、文字数を明示的に指定して処理することが必要です。
'A'以下のBCDを表示するには、以下のコマンドを実行してください
[コマンド]EVAL *ptr_a:f
[結 果 ]*ptr_a:f = ABCD
:fは先頭のアドレスからNULLまでを表示します。文字列の最後にNULLが必要です。
[コマンド]EVAL *ptr_a:s
[結 果 ]*ptr_a:s = "ABCD"
:sは、先頭のアドレスからNULLが現れるか,20文字(省略値)まで表示する。
20文字以上表示したいときには”:s 文字数”と記述します。
[コマンド]EVAL *ptr_a:x 5
[結 果 ]00000 C1C2C3C4 00000000 00000000
00000000 - ABCD............
:xは16進表示の指定です、指定された5文字分を16進で表示しています。
この状態のメモリーは以下のようになっています。
メモリーアドレス 値
&ptr_a D3E8C9B064000320 FB76AE82AF001008
*ptr_a FB76AE82AF001008 'A'
*(ptr_a + 1) FB76AE82AF001009 'B'
*(ptr_a + 2) FB76AE82AF001010 'C'
*(ptr_a + 3) FB76AE82AF001011 'D'
*(ptr_a + 4) FB76AE82AF001012 00(NULL)
|
ポインターに関しては以下のことを覚えてください。
-
変数に*をつけて定義したらポインターになる
-
変数に&をつけたらアドレスを戻す
-
*とポインター変数で、ポインターがさしている先の値を表す
ところで、ポインターは何かを差していなければなりません。
何も差していないポインターを使うとどうなるでしょうか?
ポインターを使ったPGM POINT2
/*-----------------------------------------------------------*/
/* PROGRAM-ID : POINT2 */
/* REMARKS : ポインターを使ったPGM */
/* AUTHOR : Y.Ide */
/* DATE-WRITEN : 98/04/30 */
/* VERSION : 01.00 ORIGINAL */
/*-----------------------------------------------------------*/
/* malloc */
/* ポインターにメモリーを割り当てる */
#include<stdio.h>
#include<stdlib.h> /* malloc使用のため*/
void main(){
/*ポインターの定義*/
char *ptr_a;
char *ptr_b;
/*ポインターAにメモリーを割り当てる */
ptr_a = malloc(5);
printf("ptr_a = %s \n",ptr_a);
/*メモリーが割り当てられていないポインターを使うとどうなるか?*/
printf("ptr_b = %s \n",ptr_b);
}
|
上記のptr_aには、関数malloc(5)を使用して5バイトのメモリーを割り当てていますが、ptr_bには特になにも割り当てていません。この状態のポインターは何も差していない(ポインター値はNULL)です。
NULLポインターまたは不正なポインターをプログラムで使用するとどうなるでしょう
おそらく、printf("ptr_b = %s \n",ptr_b);のところで終了するはずです。
JOBLOGをみてエラーメッセージなど確認してみてください。
AS/400のメッセージの出方は独特なので、ピンと来ないかもしれませんが、OSのエラーが起こったときに見た事があるようなメッセージじゃないですか?
次のプログラムを見てください
ポインターを使ったPGMもどき POINT3
/*----------------------------------------------*/
/* PROGRAM-ID : POINT3 */
/* REMARKS : ポインターを使ったPGM */
/* AUTHOR : Y.Ide */
/* DATE-WRITEN : 98/04/30 */
/* VERSION : 01.00 ORIGINAL */
/*----------------------------------------------*/
/* &はアドレス演算子です。 */
/* 変数のメモリーアドレスをかえします */
/* */
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
void main(){
/*ポインターの定義*/
char *ptr_a;
/*配列の定義*/
char DATA[5] = "ABCD";
/*配列DATAのメモリーアドレスをポインターに代入*/
ptr_a = &DATA[0];
printf("ptr_a = %s \n",ptr_a);
printf("DATA = %s \n",DATA);
/*配列の値を変更*/
DATA[0] = 'X';
DATA[1] = 'Y';
DATA[2] = 'Z';
DATA[3] = '?';
printf("ptr_a = %s \n",ptr_a);
printf("DATA = %s \n",DATA);
}
|
今度は、配列DATA[4]を定義して、そのアドレスをポインターに代入しています
[注]Cの配列の添字は、0から始まります
配列DATAの値を変更すると、当然それを差しているポインターの内容も変わります
同じところを参照しているのだからです
DATAの内容を変更して、*ptr_aを表示すると、DATAと同じ出あることがわかると思います
RPGで書くとどうなるでしょう。RPGにはポインターがないので完全に同じにはもちろんできませがこんな感じでしょうか
ポインターを使ったPGMもどき POINT3R
H Y/ 1
H*---------------------------------------------------------------*
H* PROGRAM-ID : POINT3R *
H* REMARKS : ポインターを使ったPGMもどき *
H* AUTHOR : Y.IDE *
H* DATE-WRITEN : 98/04/30 *
H* VERSION : 01.00 ORIGINAL *
H*---------------------------------------------------------------*
E DATA 1 5 1
I DS
I 1 5 PTR#A
I 1 5 DATA
C*
C DSPLY PTR#A
C*
C MOVE 'X' DATA,1
C MOVE 'Y' DATA,2
C MOVE 'Z' DATA,3
C MOVE '?' DATA,4
C*
C DSPLY PTR#A
C*
C SETON LR
C RETRN
C*
**
A
B
C
D
|
-
DATAは1バイトのテーブルで5個の要素をもち、初期値を”ABCD”として持ってます。
-
PTR#Aという文字フィールドをDSで同じ場所に定義しています。これで、PTR#AとDATAとは同じメモリーアドレスをさしているはずです。
-
DATA,1の内容を変更すると、PTR#Aの値も変わります。同じところを参照しているからですね
[注]RPGの配列の添字は、1から始まります
同じような例ですが
ポインターを使ったPGM POINT4
/*---------------------------------------------------------------*/
/* PROGRAM-ID : POINT4 */
/* REMARKS : ポインターを使ったPGM */
/* AUTHOR : Y.Ide */
/* DATE-WRITEN : 98/04/30 */
/* VERSION : 01.00 ORIGINAL */
/*---------------------------------------------------------------*/
/* ポインターとポインター */
/* */
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
void main(){
/*ポインターの定義*/
char *ptr_a;
char *ptr_b = "ABCD";
/*ポインターBの内容をポインターAに代入 */
ptr_a = ptr_b;
/* STRDBGでこの上の行で停止しptr_a,prt_bの値を見て下さい*/
printf("ptr_a %s \n",ptr_a);
printf("ptr_b %s \n",ptr_b);
/*ポインターAの内容のみ変更*/
ptr_a??(0??) = 'X';
ptr_a??(1??) = 'Y';
ptr_a??(2??) = 'Z';
ptr_a??(3??) = '!';
printf("ptr_a %s \n",ptr_a);
printf("ptr_b %s \n",ptr_b);
}
|
-
ptr_aとptr_bは同じアドレスを指しますので、同じ値を持っています
-
&ptr_aと&ptr_bはそれぞれ自分自身のアドレスを示します。持っているアドレスは同じでも格納している場所は別々です。
また、RPGで書いてみましょう?
ポインターを使ったPGMもどき POINT4R
H Y/ 1
H*--------------------------------------------------------*
H* PROGRAM-ID : POINT4R
H* REMARKS : ポインターを使ったPGMもどき
H* AUTHOR : Y.IDE
H* DATE-WRITEN : 98/04/30
H* VERSION : 01.00 ORIGINAL
H*--------------------------------------------------------*
E PTRA 5 5 1
E PTRB 5 1
I DS
I 1 5 PTRA
I 1 5 PTRB
C*
C MOVE 'X' PTRA,1
C MOVE 'Y' PTRA,2
C MOVE 'Z' PTRA,3
C MOVE '?' PTRA,4
C*
C DSPLY PTRB,1
C DSPLY PTRB,2
C DSPLY PTRB,3
C DSPLY PTRB,4
C*
C SETON LR
C RETRN
C*
**
ABCD
|
残念ながらこのへんで限界のようですね、
-
RPGでは、C仕様書でptr_a = ptr_bのように他の変数と同じエリアを参照させる命令がありません
-
RPGでは、moveコマンドを用いて値をコピーするしかありません。この場合はもちろん同じ値を別々のエリアに格納することになるので、片方変更したからといって、もう一方にはなんの影響もありません。
-
RPGでは、変数自身がもつアドレスを参照したり設定したりってことはできません。でも内部ではもちろん使用しています。プログラマーからは完全に見えなくしているだけです。
それでは、RPGからCのプログラムを呼び出してパラメーターを渡してみましょう。
[注]原則Cではパラメーターは値渡しです、RPGでは参照渡しです。
ポインターを使ったPGM POINT5
/*---------------------------------------------------------*/
/* PROGRAM-ID : POINT5 */
/* REMARKS : ポインターを使ったPGM */
/* AUTHOR : Y.Ide */
/* DATE-WRITEN : 98/04/30 */
/* VERSION : 01.00 ORIGINAL */
/*---------------------------------------------------------*/
/* RPGから呼ばれるプログラム */
/* RPGとCはポインター渡しでパラメーターを受け取る */
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
void main(int argc, char *argv[])
{
/*ポインターの定義*/
char *ptr_a;
/*パラメーター取得*/
ptr_a = argv[1];
printf("PRAM = %s \n",ptr_a);
}
|
-
main(int argc, char *argv[])の部分が、パラメータ受取のための定義になります、RPGだと*ENTRY
PLISTに相当します
-
argcに受け取ったパラメーターの個数が入ります
-
*argv[]はポインターを格納する配列となります。
*argv[]の構造は、以下のようになります
パラメーター1 ”AAA” メモリーアドレス 100−103
パラメーター2 ”BBB” メモリーアドレス 200−203
パラメーター3 ”CCC” メモリーアドレス 300−303
argvのアドレスが001だとすると
アドレス |
値 |
変数 |
001 |
100 |
argv[1] |
002 |
200 |
argv[2] |
003 |
300 |
argv[3] |
004 |
NULLまたは不定(何が入っているかは解らない |
argv[4] |
100 |
”A” |
parm1 |
101 |
”A” |
|
102 |
”A” |
|
103 |
NULLまたは不定 |
|
200 |
”B” |
parm2 |
201 |
”B” |
|
202 |
”B” |
|
203 |
NULLまたは不定 |
|
300 |
"C" |
parm3 |
301 |
"C" |
|
302 |
"C" |
|
303 |
NULLまたは不定 |
|
[注1]アドレスは解りやすくするために実際のアドレスよりも桁数が少なくなっています
[注2]argv[0]には、プログラム名が入ります
H Y/ 1
H*-----------------------------------------------------*
H* PROGRAM-ID : POINT6R
H* REMARKS : Cを呼び出すPGM
H* AUTHOR : Y.IDE
H* DATE-WRITEN : 98/04/30
H* VERSION : 01.00 ORIGINAL
H*-----------------------------------------------------*
E PTRA 5 5 1
E PTRB 5 5 1
I DS
I 1 5 PTRA
I 6 10 PTRB
I 11 15 DATA1
I 16 20 DATA2
I 21 21 NULL
C*
C MOVE *LOVAL NULL
C MOVE 'DATA1' DATA1
C MOVE 'DATA2' DATA2
C*
C CALL 'POINT5'
C PARM PTRA
C*
C CALL 'POINT5'
C PARM PTRB
C*
C CALL 'POINT5'
C PARM DATA1
C*
C CALL 'POINT5'
C PARM DATA2
C*
C CALL 'POINT5'
C PARM PTRA
C*
C SETON LR
C RETRN
C*
**
PTRA
**
PTRB
|
-
RPGは参照渡しなので、自分のもつ変数のアドレスをパラメーターとして渡します。
-
Cでは、パラメータをポインターとして扱い、ポインターからNULLまでを表示しています
-
RPGから渡しいてるパラメーターは一つだけですが、DSの定義のためにフィールドが連続しています。そのため、各先頭のフィールドをRPGから渡すと、それ以降のフィールドの値もCで読めています。
|