標準TTLだけ(!)でCPUをつくろう!(組立てキットです!)
(ホントは74HC、CMOSなんだけど…)
[第279回]

●RS232C受信プログラムの説明です(2)

前回からRS232C受信プログラムの説明に取りかかってしまっています。
C++のプログラムです。
C++は「つくるCPU」のテーマではありません。
なんたって8080のハード回路がメインのテーマなのですから、本来はC++などまったく「お呼びではない」ところです。
ですから、「ここはC++のお勉強室ではありませんから、C++のプログラムについてはパスします」と言ってしまってもよいところなのですけれど…。

なにしろ、道草が好きなものですから、見事に軌道を外れてしまいました。
ただいま脱線中です。
しばらくこの状態がつづきます。
まま、気持ちを大きくもっていただいて、しばし、お付き合いをお願いいたします。

以前どこかに書きましたが、C++(Cでも同じ)については、まだ初心者の域を出ていません。
まだまだわからないところだらけです。
ですから、「説明」などとえらそうなことを見出しに書いておりますが、本当のところはといえば、自分のための備忘録みたいなものです。

ただ、仮にも他人に説明しようとすると、それなりに論理を整理しなければならなくなりますから、結果的に自分自身の理解が深まる、という好結果が得られます。
まあ、その程度のものである、と考えていただいて、ざっと流し読みをしていただければ幸いです。

●コマンドライン引数

さてさて、説明のためにRS232C受信プログラムのプログラムリスト(r232.cpp)をながめていましたら、あれえ?というところが出てきてしまいました。
main( )の最初のところです。


void main(int argn,char *args[])
{
fname=args[1];

なにしろ基礎があやふやなものですから、プログラムを書いたときはわかったつもりでいても、しばらく時間が経つと、みごとに化けの皮がはがれて、わけがわからなくなってしまいます。

普通一般のC++プログラムでは、main( )と書くところを、()の中にごちゃごちゃ書いています。
これが、コマンドライン引数なるものであることは、かろうじて思い出しました。

DOSプロンプトの中で、r232.exeを実行させるときに、受信したデータを格納するファイル名を指定するためです。
でも、そうしなければいけないことはありません。

一般的なWindowsアプリケーション(GUI)ならば、起動すると、まずメッセージBOXが開いて、「保存するファイルを指定してください」とかなんとかするのが普通です。
コンソールアプリケーション(CUI)だって、起動したら、
「保存するファイル名を入力してください」
とメッセージ出力して、そこでファイル名を入力するようにしても全く構いません。

でも今回のプログラムでは、起動した最初に1回プログラム名を入力するだけで、他に入力すべきデータなどはなにもありません。
それなのに、起動するたびに、「保存するファイル名を入力してください」というメッセージが出て、そこでファイル名を入力する、というのも鬱陶しいはなしです。
ま、そう感じるかどうかは、個人の感性の問題なのですけれど、私としては、ここは断然、
r232 ファイル名[Enter]
とやって受信プログラムを起動させたい、と思ったのです([第189回]でご紹介した送信プログラムw232.exeも同じです)。

そういう余計なことを考えたりするから、こうしてまた余計なところでつまづいてしまうんですよねぇ。
全くもって、身から出た錆、です。

で、お話を元に戻します。
たとえばDOSプロンプトの中で、普通にプログラムを起動する場合には、
>r232[Enter]
のようにするのですが(拡張子.exeは省略して構いません)、それを
>r232 data.txt[Enter]
のようにしたい場合に、上のようにmain()の中に、コマンドライン引数を記述します。

今回はファイル名を指定するだけですから、引数は1個ですけれど、ここにいくつも引数を並べることもできます。
たとえば、
>testprog aka kiiro midori[Enter]
のようにすることもできます。
当然、main()に記述する引数は、その複数の値(文字型になります)を格納できるものでなければなりません。

そこで、最初のmain()に戻ります。

void main(int argn,char *args[])

ここで、args[]には*がついています。
ということは、これは文字型ポインタの「配列」である、ということのようです。
これはどういうことか?
たとえば、
>testprog aka kiiro midori[Enter]
の実行によって、入力されたパラメータが、メモリ上のある場所に、図のように格納された、と仮定した場合、args[]には、各文字列の先頭アドレス(ポインタ)が入っている、ということのようです(おお、なんという、自信のなさ!)。

そしてint argnには、引数の個数が入ります。

と、ここまでのところで、素朴な疑問です。
char *args[]が配列の宣言だとするならば、その大きさを指定する必要があるのではないか?
ということです。
だって、コンパイルした時点では、いくつの引数をユーザーが入力するかはわからないわけですから、通常は、考えられる最大個数を割り当てる必要があるのでは?というのが、機械語派である私の素朴な疑問です。

いろいろ調べてみましたが、よくわかりませんでした。
多分、ここの部分は動的なメモリ配置がされるのでしょうね。
まあ、このように記述して、コンパイルしても、なんのエラーメッセージも出ませんし、それを実行すれば、期待通りの結果が得られますから、ここは、こう書くべし、というルールだと覚えておきましょう。

さて、疑問はもうひとつありました。

fname=args[1];

って、あれえ?args[0]の間違いではないのぉ?

いや、これは、確認してみましたら、これで良かったのです。
もう、ほんとに、全部きれいに忘れてしまいますから、何回でも同じことを学習することになります。なかなかにしんどいことです。

上の文字列の格納場所とargs[]の図ではわざと外してあるのですが、じつはコマンドライン引数の一番目には、プログラム名そのものが入れられるのです。
>testprog aka kiiro midori[Enter]
の例では、
args[0]には、文字列testprog\0へのポインタが格納されます。
args[1]には、aka\0へのポインタが、args[2]にはkiiro\0へのポインタが入ります。

なお、fnameもポインタです。
文字列は配列の形で記憶されていて、数値変数のように、abc=xyzのような代入方法はできません、ということになっています。
しかし、
fname=args[1];
のように、ポインタを使うことで、文字列を一括して代入することができます。
厳密に言うと文字列を代入するのではなくて、ある文字列を示すポインタ(その文字列の先頭アドレス)情報を別の文字列のポインタにcopyしているだけなのですが。
おわかりになりますでしょうか?

●文字列のポインタ

文字列とそのポインタの関係をあきらかにするために、簡単なテストプログラムを作って確認をしてみました。
ほんとにポインタというのはややこしい、です。
いや、ポインタがややこしいというよりも、ポインタに関するC++の記号表現がわかりにくいのですよね(と私には思えます)。


/// test pointer of character
// 09.7.15
#include <stdio.h>
//
char moji1[]="ohayo!";
char *q="hellow!!!";
char *p;
main()
{
        printf("%s\n",moji1);
        p=q;
        printf("%s\n",p);
        p=moji1;
        printf("%s\n",p);
        p=q+2;
        printf("%s\n",p);
        p=&moji1[3];
        printf("%s\n",p);
}
文字型のポインタ p に注目してください。
ポインタというのは、文字列の格納場所を示す、インデックスレジスタのようなものだ、という理解ができると、このプログラム例のようなことができるのではないか、という推測が成り立ちます。
コンパイル及び実行の結果は、その推測を裏付けるものでした。

プログラムの実行結果です。


●コマンドライン引数のテストプログラム


/// test pointer of character
// command line parameter
// 09.7.15
#include  <stdio.h>
//
main(int argc,char *argv[])
{
	int i;
	for (i=0;i<argc;++i)
	{
	printf("%d,%s\n",i,argv[i]);
	}
}

プログラムを実行した結果です。

argv[0]には、プログラム名(TEST2.EXE)だけではなくて、
C:¥CPP¥TEST¥TEST2.EXE
という文字列が読み込まれていることがわかります。
ということは、コマンドライン引数というのは、キー入力バッファに入力された文字列(行の先頭から[Enter]キー入力までの文字列)を先頭からサーチしていって、区切り記号(デリミタ、空白)ごとに、文字型配列に読み込んだものであろうと思われます。

上のプログラムのデータ表示部分に少し追加をして、ポインタの正体も表示させるようにしてみました。


/// test pointer of character
// command line parameter
// 09.7.15
#include  <stdio.h>
//
main(int argc,char *argv[])
{
	int i;
	for (i=0;i<argc;++i)
	{
	printf("%d,%d,%s\n",i,argv[i],argv[i]);	}
}

実行結果です。


argv[0]の値(文字列C:¥CPP¥TEST¥TEST3.EXE\0の先頭アドレス)だけが、その他の文字列を示すポインタの値と離れています。
プログラム名を含む文字列なので、他の一般的なパラメータとは扱いが異なっているのだと思います。

ああ。本日も全く横道にそれてしまって、本題のRS232C受信プログラム部分には入れませんでした。
2009.7.15upload

前へ
次へ
ホームページトップへ戻る