C言語プログラミング

1.プ ロ グ ラ ミ ン グ の 大 要

(a)Cープログラム ∋ 関数、変数。 (b)関数と変数の宣言、または定義。 使用する前に 宣言 [属性、名前] または、 定義 [属性、名前、メモリ空間への割り当て] しなければならない。 defaultは、int (char) 属性となる。従って、それ以外の時は、必ず、 宣言しなければならない。 (c)属性 型指定子 void;unsigned;char,short(int),long,double(float) 記憶クラス指定子 default,auto,static,extern,register,typedef ポインタ指定子、配列指定子 (d)宣言定義の種類 内部宣言・定義 default=auto; static=その関数のみ有効。 extern= その関数外にある外部宣言・定義が関数内 で有効 外部宣言・定義 default=linkerが用意したメモリ領域に出来、プログラム全 部に有効。 static= そこから、プログラム・モジ−ルの終わりまで有効。 extern= そのモジュール外にある外部宣言・定義がモジュー ル内で有効

2.プログラミングおける注意

(a)命令を一行で書けないときは、¥ 例: printf("ABC ...................................\ DEF"); (b)関数定義時の仮変数 int, char 仮変数は、 xyz (int x, int y, char z) と書くことが出来る。しかし、float 仮変数は, xyz (x, y, z) float x, y, z; {.... と書かなければならない。 (c)relational operators 不等号から書く。 <=,>= でなければならない。 =<,=> は、エラアとなる。 (d)変数操作において変数 変数は変数番地の内容を表す。 a=1000 は a 番地の内容に 1000 を入れること。 &a は aの番地(offset)を表す。 *a は aの間接指定を示す。 (e)long & double integerの代入、出力: long dd; dd=1000L=(long)1000; printf("%ld", .....); double ddd; ddd=10.0=(double)10; printf("%g", .....); scanf("%lf", ddd) ....”l”を忘れるな! (f)malloc(size) 関数 MSCは、DS(SS)の高位から低位にデ−タ、スタックを割り付ける。 その後に、 のデ−タが確保出来るかを調べる。 出来なければ、0を返す。 出来れば、先頭番地(オフセット)を返す。 (j)キャスト(cast) 宣言 char far *a; int i; a は char far 型のポインタ(*) である。 −−−−−−− 関数 a=(char far *)0xa000 8000 キャスト セグメント:オフセット値 0xa000 8000 と言う数字は (char far型のポインタ(*))である。 i=*a aに間接アドレス演算子を適用。それを i の内容にする。 (h)CRT画面制御命令の複合 printf(\x1b[4;17m) セミコロン ; で区切る (i)char, unsigned char 変数の判定 #include unsigned char cc; と定義したとき, if (cc== '\x0f7')... と書いて、'\x0f7' を入力しても等しくならない。 if (cc== 0x0f7)... と書かなければならない。 ∵ '\x0f7', (char)'\x0f7' は、符号付き8bitsとみなされる。それで、 == 判定時にも符号付きと見なされる。等号判定は、16K^/D でおこなわれる。 それで、まず、定数を8K^/D→16K^/D変換する。このとき、 f7 → fff7(-8) となる。それに反して、(unsigned char)CC は f7 → 00f7 となる。 (j)ゼロ拡張: 代入の際の型の変換 <図1>

3.ポインタとデ−タ・アレイ

宣言(定義): *X は、X が ポインタであることを宣言。 関数内: X pointer の content ポインタの内容。 &X address operator Xのアドレス。 *X indirect operator 間接アドレス指定演算子。 ポインタには、 ┌─────────┬───────────────┐ │指されるものの型 │ ポインタの型 │ ├─────────┼───────────────┤ │ char │ *AA │ │ void * │ *A (-> double pointer) │ │ int │ far *BB; │ │ long │ far *CC; │ │ void far * │ (far *DD); │ └─────────┴───────────────┘ 最初の例では、AA は default(near) pointer(2 bytes) である。また指し示 すものものは、 char(1 byte)である。 二番目の例では、A は near pointer(2 bytes) である。指し示すものものも、 near pointer(2 bytes)である。 最後の例では、DD は far pointer(4 bytes) である。また指し示すものものも、 far *(4 bytes)である。 void far * (far *DD); ....宣言 DD =(void far *)0x080; ....操作 *DD =0x0123456L; とすると、00:0x080 番地から、56 34 12 00 が、割り付けられる。これは、 mov WORD PTR [bp-4],80h ;DD mov WORD PTR [bp-2],0 sub bx, bx mov es, bx (es=0) mov bx, 128 (bx=80h) mov WORD PTR es:[bx], 5634h mov WORD PTR es:[bx+2], 12h と展開される。 bp-4 bp-2 bp ┌──┬──┬──┬──┐ │80│00│00│00│ └──┴──┴──┴──┘ ┌──┬──┬──┬──┐ 0000:0080:│56│34│12│00│ └──┴──┴──┴──┘ (a)Data array では、添え字は、整数の定数か、変数のみ。 関数不可。 A[i+1] OK A[strlen(x)] error. (b)データアレイを宣言と定義を同時にする。 char A[]="ABC"; int A[5]={12, 34, 56}; にように、宣言・定義する時、 型は、extern か static でなければならない。 (c)indirection & address operator int *p, x, y; p=&x; y=*p ならば、 x=y ┌───┐ &p: │ p=&x │ └───┘ │ ┌─────┐ └─> &x:│x =123 │ ├─────┤ │ │ │ │ ├─────┤ &y:│y= *p =x │ └─────┘ (d) 宣言時、int (char) p, *p, P[3] の相違 p : p は、変数。 *p : p は、ポインタ。 P[3]: P は、番地指定語。変化出来ず。 <変数> char p; ┌─────┐ &p:│p= 'A' │ └─────┘ <ポインタ 対 番地指定語> char *p; P[3] ="ABC"; p=P のとき。 ┌─────┐ &p: │p= P 番地 │ ───┐ │ │ │ │ │ │ &P: │P 番地 │─┐ │ └─────┘ │ │ │ │ │ │ ┌─────────┐ └> └> P : │ P[0]='A' =*p │ ├─────────┤ P+1:│ P[1]='B' =*(p+1) │ ├─────────┤ P+2:│ P[2]='C' =*(p+2) │ ├─────────┤ │ │ (e)複合アレイ: ◆a[m][n] j列 → (この順にメモリに割り付けられる) a[0][0], a[0][1],...... a[0][j],....... a[0][n-1], a[1][0], a[1][1],...... a[1][j],....... a[1][n-1], ................................................. i ................................................. 行 a[i][0], a[i][1],...... a[i][j],....... a[i][n-1], .................................................. ↓ .................................................. a[m-2][0], a[m-2][1],.....a[m-2][j],..... a[m-2][n-1], a[m-1][0], a[m-1][1],.... a[m-1][j],..... a[m-1][n-1], 従って、a[i][j] =*(a +n *i +j) となる。 ◆ ポインタ加算の注意 int AA[2][3]= {1, 2, 3, 4, 5, 6, } のとき、3 をポイントするには、 AA[0][2] か、(int *)AA +3*0 +2; 単に、(AA +2) と書くと、 =AA[2][0] と等価となる。それで、ポイントするのは、 AA+9 番地 となる。 AA+2 番地をポイントするには、上述のように、 (int *)AA +2; とする。 (f)Call by reference 対 Call by value sub-関数に入ったとき、 main-変数その物をいじる。 value を sub に移す [例] main() { int x=10; aaa(&x); printf("%d\n", x); } aaa(int *p) { *p=1; } pointer p が指している番地(p の内容)は、int x の番地である。すなわち、p ointer p は、x 番地を指している。その x 番地の内容(即ちx)を1とする。 (g)ポインタ配列 と ダブル・ポインタ 宣言(定義): char *P[3], **p; p=P; Pは、[3]であるから 配列。 型は、<*>であるから、near pointer。しかも、 それは、charを指す(=つまり、offset+1ずつ増減する)。 pは (*p)であるから near pointer。 型は、*()であるから、 near pointer。そのpointerは、charを指す。 操作: ┌─────┐ ┌──────┐ ┌───────┐ &p:│p= P 番地 │──> │P[0] =*p │ ──> │*P[0] =**p │ └─────┘ ├──────┤ ├───────┤ │P[1] =*(p+1)│──┐ │ │ ├──────┤ │ │ │ │P[2] =*(p+2)│┐ │ │ │ └──────┘│ │ │ │ │ │ ├───────┤ │ └>│*P[1] =**(p+1)│ │ ├───────┤ │ │ │ │ ├───────┤ └──>│*P[2] =**(p+2)│ └───────┘ (f)メイン・モジュルのアーギュメント : ポインタ配列または、ダブル・ポインタとなっている。 main(argc, argv, envp) の時、 int argc; 入力ア−ギュメントの数 char *argv[]; or **argv; 入力ア−ギュメントのポインタ char *envp[]; or **envp; 環境のポインタ char **argv or *ARGV[] ┌─────┐ &argv :│ argv, │ &&ARGV:│ &ARGV[0] │┐ └─────┘│ │ │ ┌──────┐ └> argv: │ *argv, │──> アギュ1 &ARGV[0]: │ ARGV[0] │ ├──────┤ argv+1:│ *(argv+1),│──> アギュ2 &ARGV[1]: │ ARGV[1] │ ├──────┤ │ . │ │ . │ │ . │ (g)far ポインタと near ポインタ セグメント内のデータは、オフセット値を、nearポインタ(2バイト)、セ グメント外のデータはセグメント値:オフセット値を、far ポインタ(4バイ ト)で参照する。 ディフォルトのポインタ: スモール・モデル: near ミディアム・モデル: コード・ポインタは far、データポインタは near ラージ・モデル: far ポインタ farポインタは次のような4バイトからなり、上位2バイトでセグメント値、下 位2バイトでオフセット値を表している。 a=(char far *)0xa0008000L /* 物理アドレス 0xa8000 */ farポインタからセグメント値とオフセット値を分離するには、 FP_SEG(a) ・・・0xa000 が返される。 FP_OFF(a) ・・・0x8000 が返される。 (h)far ポインタと far data static char far buf[100]; /* farデータ */ char far *pa; /* farポインタ */ と宣言されていたとき、paは4バイトのポインタ変数、bufは4バイトのデータ・ アレイ。 farポインタpaを用いてfarデータbufを参照するには、 pa=(char far *)buf; farポインタpaは、自動変数としてスタック上にとられる。 farデータのbufは、DGROUP とは異なる far データ・セグメントに取られる。 ┌───────────┐ プログラム│ _TEXT │ 領域 ├───────────┤ │ farデータ・セグメント│ ← ここにbuf[100]の領域がとられる。 ├───────────┤ │ NULL │ │ │ ├───────────┤ DGROUP │ _DATA │ 静的または外部データ │ │ ├───────────┤ │ CONSTANT │ ├───────────┤ │ _BSS と c_common │ ├───────────┤ ←far ポインタpaは変数なので │ │ ここに割り当てられる。 │ STACK │ ├───────────┤ ├───────────┤ │ ヒープ │ │ │ └───────────┘

4.構造体、共用体

構造体: 項目ごとに、次々に領域を取る。 struct WORDREGS { <-- 構造体タグ(=構造体名) unsigned int ax; unsigned int bx; <-- 構造体メンバー unsigned int cx; } A[3], <--構造体配列 x, y, z; <--構造体ポインタ 構造体タグ(=構造体名):別のモジュールで、外部宣言するとき、同じ構造 のものと見なされる。 (1つのモジュール内のみ必要のとき、省略可。) 構造体メンバー:偶数番地から割り付けられる。 struct { char x; char y; char z; } の時 ┌─┬─┬─┬─┐ │x │0 │y │z │ となる。 └─┴─┴─┴─┘ 構造体配列:各メンバーは、A[2].ax のように表す。 構造体ポインタ:構造体ポインターは、その構造の先頭を指し示す。 (丁度、char pointer が、 char string の先頭を指すように)。 従って、x+1 (=x+1 -> ax) は、x の sizeof(struct WORDREGS) 番地 (バイト)目を指す。 共用体: 全項目、同じ先頭番地から取る領域を取る。 構造体、共用体の典型的な例は、レジスタ・セット。 (a)セグメント・レジスタ: 構造体 struct SREGS{ タグ (TAG) unsigned int es; unsigned int cs; unsigned int ss; unsigned int ds; }; と、宣言される。それで、 struct SREGS segregs <-- 宣言子 (DECLARATOR) と定義し、個々のレジスタは、メンバ−として、 segreg.ds; segreg.cs; segreg.ss; segreg.es と表す。 (b) その他のレジスタ: 共用体 struct WORDREGS { struct BYTEREGS { unsigned int ax; unsigned char al, ah; unsigned int bx; unsigned char bl, bh; unsigned int cx; unsigned char cl, ch; unsigned int dx; unsigned char dl, dh; unsigned int si; }; unsigned int di; unsigned int cflag; }; union REGS { struct WORDREGS x; struct BYTEREGS h; }; と共用体として、宣言し、さらに、 union REGS regset; と定義し、個々のレジスタは、メンバ−として、 regset.h.al; regset.h.ah; regset.x.ax .... と表す。 ┌─────┐ regset.h.al │ │ ├─────┤ regset.x.ax regset.h.ah │ │ ├─────┤ regset.h.bl │ │ ├─────┤ regset.x.bx regset.h.bh │ │ ├─────┤ regset.h.cl │ │ ├─────┤ regset.x.cx ・ │ │ ・ ├─────┤ ・ │ │ ・ │ │ └─────┘ (c).レジスタ構造体 SREGS、共用体 REGS を使う例 ◆セグメント・レジスタ群の構造体 SREGS への読み込み segread(&segregs); とする。 ◆ソフトウェア・割り込み int86x(vect, ®set, ®set, &segregs); ベクトル、入力レグセット、出力レグセット、入力セグ・レグセット とする。 ◆メモリ間のブロック転送 movedata(sourceseg, sourceoff, destseg, destoff, nbyte); ソース・セグメント、オフセット → デステネイション、 バイト数

5. ファイル処理

[a]ファイル名 ドライブ名:¥パス名¥ファイル名を、 C言語で書くには、 −>”ドライブ名:¥¥パス名¥¥ファイル名” [b]ファイルの種類 ◆ライト・イメージのよる分類 a.バイナリ・モード b.テキスト・モード ◆ファイル編成の種類 a.シーケンシャス・ファイル b.ランダム・ファイル c.レコード型ファイル ◆ファイルI/O による分類 a.低水準ファイル入出力 b.高水準ファイル(ストリーム)入出力 [c]ライト・イメージ(モード) テキスト・モード 印字可能文字とコントロール・コード('\t', '\t'など)からなる。 たとえば、fprint(fp, "%d",10) --> ...31 30 ... エデタで出来るテキスト・ファイルはこのモード。 メモリ・ファイル間で CR・LF−'\n' の自動変換。0x1a→EOFとみなす。 バイナリ・モ−ド 00255 のすべてのコードが存在する。 たとえば、putw(10,fp) --> ...0a 00 ... *.obj, *.exe は、このモード。 メモリ・ファイル間で CR・LF−そのまま 0x1a→EOFでない 三つのモード指定法 オープン時の指定: fd=open("abc.x", O_RDONLY | O_BINARY); fp=fopen("abc.x", "rb"); デフォルトは、t-mode グローバル変数_fmodeによる指定: #include ∋ _fmode #include ∋ open _fmode=O_BINARY; として置くと、open 時のデフォルトは、b-modeとなる。 リンク時の指定: BINMODE.OBJ をLINKすると、stdin,sdout,stderr及びt-modeで オープンしたファイル以外は、b-modeとなる。 [d]ファイル編成の種類 バイト→フィールド→ レコード→ファイル。 a.シーケンシャス・ファイル ファイル先頭から逐次書き込み/読み出し b.ランダム・ファイル ファイルの任意位置への書き込み/読み出し c.レコード(構造体)型ファイル ファイルをレコード単位で扱うとき、fscanf/fprintfを用いる。 固定長レコード → ランダム・ファイルに適す 可変長レコード..区切り必要 → シーケンシャス・ファイルに適す がある。 [e]低水準ファイル入出力関数 大量のデータを高速にX0D^/W2Dする場合に有利 a.MS_DOS のシステム・コールを呼び出す関数。ファイルの識別は、ファ イル記述子を使う。これは、MS_DOS のハイル・ハンドルと同一である。 b.create 関数:ファイルの作成+オープン fd=creat (, ) ┌───────┬─────────┬─────────┐ │ mode │ テキスト・モード │バイナリ・モード │ ├───────┼─────────┼─────────┤ │リード/ライト│ 0 │ 0x8000 │ └───────┴─────────┴─────────┘ c.open 関数:ファイルのオープン fd=open (, ) ┌───────┬────────┬─────────┐ │ mode │テキスト・モード│バイナリ・モード │ ├───────┼────────┼─────────┤ │リード │ 0 │ 0x8000 │ │ライト │ 1 │ 0x8001 │ │リード/ライト│ 2 │ 0x8002 │ └───────┴────────┴─────────┘ d.ファイルの入出力。ファイル記述子を使う。 char buff[SIZE]; read (fd, buff, nbytes); write (fd, buff, nbytes); e.close 関数 close (fd); f.ファイル位置の移動/取得 lseek (fd, offset, mode) 正常ファイル位置、異常-1L 0: 先頭から 1: 現在位置から 2: 終わりから ftell g.その他 ファイル・エンドの検知 eof ハンドルのコピー dup/dup2 特殊なオープン sopen ▼低水準ファイル <図2 [f]高水準ファイル入出力関数 文字/行単位で処理可能のバッファリング付きのためC7=D・L'2Yの扱いに有利の関 数。これには、バッファド・コンソル入出力関数が含まれる。 a.ファイルの識別: ファイル(構造体)ポインタ。 ファイルに関する構造体は、 struct _iobuf { /* = FILE と define されている。*/ char *_ptr; /* カレント・バッファ・ポインタ */ int _cnt; /* バッファ内の有効データ数 */ char *_base; /* I/O バッファのベースアドレス */ char _flg; /* コントロール・フラグ */ char _file; /* ファイル宣言子 */ } _iob [_NFILE]; /* 標準入出力構造体ポインタ配列 */ と定義されている。_NFILE=20 この構造体のポインタは、 struct _iobuf(or FILE) *fp; と宣言すればよい。 また、標準入出力の stdin, stdout, stdaux, stdprn ポインタは、構造体ポイ ンタ 配列 _iob で、 #define stdin (&_iob[0]) #define stdout (&_iob[1]) #define stdaux (&_iob[2]) #define stdprn (&_iob[3]) と定義されている。それで、_iob[0]._ptr とすれば、stdin のバッファを直接ア クセス出来る。 b.ファイルのオープン/クローズ fopen/fclose/fcoseall/freopen FILE *fp0, fopen(); fp=fopen (, mode) ...エラ−時, NULL 第一字 r: あるものをオープン w: (あれば、削除し、)新空ファイル作成 a: 無ければ、新空ファイル作成 第二字 b: バイナリ・ファイル t: テキスト・ファイル c.ファイルの入出力。 ◆1文字単位の入出力 getc/fgetc/putc/fputc/unget (c, fp); c= putc/fputc (fp); ◆1ワード単位のリード/ライト getw (c, fp); c=putw (fp); ◆1行単位の入出力 fgets (buff, maxbytes, fp); fputs (buff, fp); ◆書式付き入出力 fscanf/fprintf ◆リード/ライト? fread/fwrite x バイトを n 個 fread(buff, x, n, fp); ◆バッファド・コンソール入出力(リダイレクト可) getchar/fgetchar (c); c=putchar/fputchar(); gets (sbuff, nbytes); puts (sbuff, nbytes); scanf ("%format",data); printf ("%format",data); e.ファイル位置の移動/取得 fseek/rewind/ftell fseek (fp, position, mode) (long) 0: 先頭から 1: 現在位置から 2: 終わりから 戻り値: 正常ファイル位置、異常-1L f.その他 ファイル・エンド・エラーの検出 feof/ferror/clearerr バッファの設定/フラッシュ setbuf/fflush/flushall 低水準とのインターフェイス fdopen/fileno ▼バッファド・ファイル <図3

6.I/O リダイレクト、フィルタ、パイフ

(a)標準入出力ファイル 標準入出力ファイルはMSC のプログラム起動時に自動的にオープンされる。 その時のファイル・ポインタおよびファイル・ディスクリプタは次の通り。 ▼標準入出力ファイル ┌───────┬─────┬─────┬───────────┐ │ ファイル │L'2Y・ │L'2Y・ │ 備 考 │ │ │ N_2]@ │ C^(=8XL_@│ │ ├───────┼─────┼─────┼───────────┤ │標準入力 │ stdin │ 0 │通常 キーボード │ │標準出力 │ stdout │ 1 │通常 スクリーン │ │標準エラー │ stderr │ 2 │通常 スクリーン │ │標準補助入出力│ stdaux │ 3 │通常 COM(RS-232C)N_0D │ │標準プリンタ │ stdprn │ 4 │通常 プリンタ │ └───────┴─────┴─────┴───────────┘ バッファド・ファイル入出力関数 gete/pute を用いて標準入出力にアクセスす るには次のようにします。 getc(stdin) ・・・stdin からの1文字入力。 getchar( )と同じ putc(c, stdout) ・・・stdout へ文字 c を出力。 putchar(c)と同じ putc(c, stderr) ・・・stderr へ文字 c を出力 getc(stdaux) ・・・stdaux からの1文字入力 putc(c, stdaux) ・・・stdaux へ文字 c を出力 putc(c, stdprn) ・・・stdprn へ文字 c を出力 (2)標準入出力ファイルの再オープン これらのファイルは、ユーザーが独自にデバイス名を用いてオープンすることも できる。ただし、ユーザーが、これらのデバイスをオープンするとき、MS−DO SがサポートするIPのデバイス名を使ってはならない。 con   ・・・コンソール(キーボード/スクリーン) aux ・・・補助入出力 prn ・・・プリンタ nul ・・・ヌル clock ・・・クロック 例; stdout下のノン・バッファリングモード→バッファリング・モードとする putc('a', stdout) = putchar('a') は '\n' がくる、こないに関係なく即座にスクリーンへ出力する。 それを1行ずつ出力するには、ファイル・ポインタ bufconを用いて、 FILE *bufcon; bufcon=fopen ("con", "wt"); putc('a', bufcon); とすると、putc('\n', bufcon); が行われるまで文字'a'はスクリーンには出力さ れなくなる。 (3)フィルタ型プログラムとi/oリダイレクト フィルタ型プログラムは、標準入出力(stdin)からデータを入力し、そのデータ を加工して標準出力(stdout) に出力する。 stdinとstdoutはi/oリダイレクト(他の入力にかえること)できる。入出力 はキーボードとスクリーンに限定されず汎用的な処理が行える。 stderrは、stdoutと同じくスクリーン出力になっている。しかし、 stderrは、リダイレクトされない。それで、stdoutがリダイレクトされても、いつ もスクリーン出力となる。 フィルタは、パイプを用いていくつでもつなげていくことができる。パイプはMS -DOSのコマンド・ラインで「|」の記号で表わす。 <図4 (4)I/O リダイレクトされないコンソール入出力 フィルタ型プログラム中、stdinやstdoutを、ファイルなどにリダイレクトすれ ば、getcha/putchar; getch/putch などの命令は、すべてI/O リダイレクトの 対象となる。しかし、このようなりダイレクト中も、ときにキーボード入力、スク リーン出力したいことがある。 このようなとき、MSC 関数を用いてI/O リダイレクトされないコンソール 入出力を行うことが出来る。デバイスCONを独自にオープンし、DUP関数によりハン ドル(ファイル・ディスクリプタ)の二重化を行い、stdinとconをうまく切り替え る。手順は次の通り。 .妊丱ぅconをオープンする。 stdin(ハンドル0)を退避する。 conをstdinにコピーする。これにより、stdinからの入力を行っている関数(g etchar,getchなど)はconからの入力になる。 conからの入力を行う。 ヂ猗鬚靴討△辰織魯鵐疋襪stdinに戻す。これによりstdinは前のリダイレクト 先に戻される。 <図5 ■リダイレクト可能な標準入出力を用いた汎用プログラム #include main() { int c; while ((c=getchar()) != EOF) putchar(c); } ●gets/puts ─────────────────── #include main() { char buf[256]; while (gets(buf) != NULL) puts(buf); } ●scanf/printf ──────────────── #include main() { int year; char name[21]; while (scanf("%s %d", name, &year)!=EOF) printf("%20s%3d\n", name, year); } ■フィルタ各種 ●タブをスペースに換えるフィルタ タブコード("\t")は、スクリーン、プリンタなどのデバイスで異なった扱いをす ることがある。たとえば、スクリーン出力を prn へリダイレクトした場合、プリ ンタにおいてタブ位置のセットアップがなければ、タブ・コードを無視してしまう。 そこで、タブ・コードをつぎのようにして、スペースに置き換える。 /*--------- タブをスペースに換えるフィルタ tabspac ----------------*/ #include main() { int c, i, count=0 while ((c=getchar())!=EOF){ if (c == '\t'){ for (i=8-count%8; i>0; i--){ putchar(' '); count++; } } else { putchar(c); count++; } if (c=='\n' || c == '\f' ) count=0; } } テキスト1行は、'\n'で終わっているものとし、countはその行の何文字目にい るかを示す。タブ位置は8文字間隔でとるものとする。'\t'が現れたら(8-count% 8)個のスペースを補う。 ファイル abc.c をフィルタtabspc を通してプリンタに出力すると、タブ位置が セットアップされていなくても正常な出力が得られる。 <図6 ●行番号を付加するフィルタ テキストを1行単位でリードし、4桁の行番号を先頭に付加するフィルタ LI NEを次に示す。 #include main { int count=1; char buf[256]; while (fgets(buf, 256, stdin)!=NULL) printf("%04d: %s", count++, buf); } ●小文字→大文字変換フィルタ 小文字を大文字に変換するフィルタtoupperを示す。 #include main() { char c; while ((c=getchar()) != EOF) putchar(toupper(c)); } このフィルタを用いて a.c のファイルを大文字変換して b.c にコピーするには 次のようする。 B>toupper < a.c > b.c ■パイプ 先に示した3つのフィルタ toupper, tabspc, line を用い、ファイル a.cの内 容を大文字に変換し、タブをスペースに変換し、さらに行番号を付けてプリンタに 出力するには A>type a.c | toupper | tabspc | line > prn とする。 ■I/O リダイレクトされないコンソール入力 以上から、I/O リダイレクトされず、常にコンソールから1文字入力する関数 getkey() は次のようになります。 getkey() { FILE *con; int std, c; if (( con=fopen("CON", "rb"))==NULL){ printf("Can't open CON.\n"); exit(); } std=dup(0); /* stdin の 退避 */ dup2(fileno(con), 0); /* stdin を con にリダイレクト */ c=getch(); /* キー 入力 */ dup2(std, 0); /* stdin の復帰 */ return(c); } この関数を利用したフィルタ more を次に示します。 #include main() { char buff[256]; int count=0; while (gets(buff != NULL){ puts(buff); if (++count==22){ printf("--- more --- Hit Any Key\n"); getkey(); count=0; } } } フィルタ more は、リードしたテキストを1画面(22行)表示し、キー入力が あるまで待ち、キー入力があれば次に進むというもの。このような場合、どうして も I/O リダイレクトされないコンソール入力関数が必要になります。 ■ I/O リダイレクトされないコンソール出力 I/O リダイレクトされないコンソール出力は、コンソール入力の場合と同様 な方法で行える。しかし、stderrに出力することで簡単に実現できる。 fprintf(stderr, "Not Text File\n");

7.Cプログラムの展開とリンク

[0]data array 展開に関する注意 データを次のように宣言する。 int a, b, c, d; int x[2]; char y[2]; アセンブラ展開は、宣言の順の通り、すなわち a, b, c, d, x[0], x[1], y[0], y[1] のようにアドレスに割り付けられない。 d, x[0], x[1], c, b, a, y[0], y[1] となる。 [1] Cプログラムの展開 スモール/コンパクト/ミディアム/ラージ/ヒュージの各モデル、基本的には同じ 構造。変数/配列宣言のとき、無指定変数(配列)をnear/far/hugeとするかが異 なる。 ●near指定あり・・・・・・・・・・デフォルト・セグメント(DGROUP) ●farまたはhuge指定あり・・・・・・クラスFAR_BSSまたはFAR_DATAに独立 したセグメントとして取られる。 ●指定なし スモール/ミディアム・・・・・デフォルト・セグメント(DGROUP) ラージ/ヒュージ/コンパクト・・・小さなデータはデフォルト・セグメン ト;大きなデータはFAR_BSS/FAR_DATAセグメントとして取られる。 ┌───────────────┐↑ 残りのメモリ全部 │ farヒープ │↓ ├───────────────┤↑ │ nearヒープ ││ │ -----------------------------││ ─┬─ 高アドレス │ スタック ││ │ SS:SP ↑ │ STACKクラス'STACK' ││  ↓ │ │ -----------------------------││ │ │ 初期化されていないデータ ││ │ │ _BSSクラス'BSS'および ││ 最大64Kバイト │ │ c_commonクラス'BSS' ││ (DGROUP) │ │ -----------------------------││ │ │ CONSTクラス'CONST' ││ │ │ 定数専用データ ││ │ │ CONSTクラス'CONST' ││ │ │ 初期化されているデータ ││  ↑ │ │ DATAクラス'DATA' │↓ │ │ DGROUP= →│ -----------------------------│↑ ─┴─ DS: │ ES=DS=SS │ 初期化されていないfar型データ││ │ │ ????_BSSクラス'FAR_BSS' ││far/hugeで指定されたデータ ↓ │ -----------------------------││最大64バイトのセグメント 低アドレス │ 初期化されているfar型データ ││が複数作られる │ ????_DATAクラス'FAR_DATA' ││ │ -----------------------------│↓ミディアム$ラージ$ヒュージ │ コード │↑最大64バイトのコード・セ │ _TEXTクラス'CODE' ││グメントがモジュールごとに CS →└───────────────┘↓作られる [2]変数の展開 a.大要 内部レベル (auto) block 中で有効 register static 外部レベルのstaticと同じ extern 外部レベルのexternと同じ 外部レベル 変数 ・static 定義とみなされる 例 static int k=16; static int k;(=static int k=0) ・extern 別の場所で定義した変数の参照を宣言(定義はできない) ・(なし) externと同じ 但しint k=1;とすれば定義となる。また、どこにも定義なければ定義となる b.public と展開されるもの ┌─────────────────────────┐ │#include │int RSIZE=75; → 外部定義は │FILE *fpo, *fopen(); │int N, N1, N9, L2; │ │main() │{ │ char fn[20], term; │ char buff[512]; │ unsigned I, ln; │ │ ginit(); │ │/*open file */ │ printf("FILENAME: "); scanf ("%s", fn); │ fpo=fopen (fn, "r"); └─────────────────────────┘ を展開すると EXTRN _atoi:NEAR EXTRN _DISPL:NEAR EXTRN _locate:NEAR EXTRN _close:NEAR EXTRN _fpo:WORD EXTRN _N0:WORD EXTRN _N1:WORD EXTRN _N9:WORD EXTRN _L2:WORD DATA SEGMENT $SG35 DB FILENAME: ', 00H EVEN $SG37 DB '%s', 00H EVEN $SG38 DB 'r', 00H $SG41 DB '%d', 0aH, 00H $SG42 DB '%x', 0aH, 00H PUBLIC RSIZE ← このように展開される _RSIZE DW 04BH ; .com _fpo,02H NEAR : .com _N0,02H NEAR ; .com _N1,02H NEAR ; .com _N9,02H NEAR ; .com _L2,02H NEAR DATA ENDS となる。 c.extern と展開されるもの ┌─────────────────────────┐ │int N0, N9; → 外部 │/********** SUB DISPL *****************************/ │DISPL () │{ │ int y, n, nn; │ char term,buf[100]; │ cls(3); │ nn=N0+21; if (nn>N9) nn=9; │ for (y=0, n=N0; n < nn;y++,n++){ │ LREAD (y,buf, &term); │ locate (1,y); printf ("%s", buf); │ TERM (term); │ } │} └─────────────────────────┘ は、 extern EXTRN _N0:WORD EXTRN _N9:WORD _DATA SEGMENT $SG39 DB '%s', 00H EVEN $SG44 DB '%c', 00H と展開されるか、defaultの時はそれ以外に ;.comm_N0, 02H NEAR ;.comm_N9, 02H NEAR 次のように書いても同じ展開は同じ。 ┌─────────────────────────┐ │/********** SUB DISPL **************/ │DISPL () │{ │extern int N0, N9 → 内部 │ .......... └─────────────────────────┘ d.static の展開 DSに割り付けられる ┌─────────────────────┐┌──────────┐ │static init N0, N9 │static int N0=5 │ │ ・ │/********** SUB DISPL **************/    │   ・ │DISPL ()                  │     ・ │{                     │    ・ │ int y, n, nn;             │    ・ │ char term,buf[100];          │    ・ │ ......... │ └─────────────────────┘└──────────┘ は、次のように展開される _BSS SEGMENT _DATA ENDS $S20_N0 DW 01H DUP (?) $S22_N0 DW 05H $S21_N9 DW 01H DUP (?) _DATA SEGMENT _BSS ENDS [3]配列の展開 static auto,default extrn ─────────────────────────────── ↓ ↓ ↓ DSにとる    STACKにとる   DSにとる 外部と同じ 外部と同じ プログラム外部に書く時 static default extrn (com部分なし) ─────────────────────────────── static char buff[100]           char buff[100] ↓ ↓ _BSS SEG EXTRN _buf:BYTE _DATA SEGMENT $S20_buff DW 32H DUP(?) BSS ENDS .comm _buf,064H NEAR _DATA ENDS [4]関数から関数へのデータの引渡し Cにおいては、1つの関数内では、bp を基準として参照される。 関数から proc (x, y, z); のように引き数 parameters をもって、関数を移行する。このとき、変数はスタッ クに積んで引き渡される。リターン後、spに引数の数を加えて、以前のbpとする。 ┌─┐ │j │bp-4 │ │ ├─┤ │i │bp-2 sp→bp │ │ bp→sp ┌──┬──┴─┴────┬──┐ │push│ bp │pop │ │bp │ │bp │ ┌───┼──┴─────────┴──┼───┐ │return│ bp+2 │return│ │addr │ │addr │ ┌─┼───┴───────────────┴───┼─┐ bp-6│z │ bp+4 │ │ │ │ │ │ ├─┤ ├─┤ bp-4│y │ bp+6 │ │ │ │ │ │ ├─┤ ├─┤ bp-2│x │ bp+8 │ │ │ │ │ │ ──┴─┴───────────────────────┴─┴── ↑sp ↑bp ↓bp ↓sp ───────────┬────────────┬────────── MAIN   │ PROC    │ MAIN [5]関数から関数へのデータの返し Cでは、関数の引数をプロセジャ内で変化させても、メインに帰ると、元の値に 戻る。(algolのvalue宣言したのと同じ) プロセジャ関数内での引数変化をメイン復帰後残すには、 ──────────────┬──┬─────────────── メイン │ │ プロセジャ ──────────────┼──┼─────────────── & (変数) を引き渡す │--> │ *(ポインタ) として受けとる。 配列のポインタを引き渡す │--> │ ポインタとして受けとる。 関数に持って帰る │<-- │ RETURN (引数) として、 ──────────────┴──┴─────────────── ds 領域は、保証されない。アッセンブラ段階でコ−デングしても、 _DATA SEGMENT M DW 10 ←MAINに帰ったとき保障されない。 _DATA ENDS _TEXT SEG . MOV A, M MOV A, CS:M . ↑ (M DW 10) ←のようにCS内で定義しなけれがならない。 _TEXT ENDS [6]配列ポインタ(および変数)の受け渡し FILE:SREAD.C ┌─────────────────────────┐ │#include │#include │ │main() │ │{ │static char buf[1024]; │int I, j, drive, sectnum, sectbgn; │ │ printf("DRIVE= ");scanf("%d", &drive); │ printf("SECTBGN= ");scanf("%d", §bgn); │ buf[0]='Z'; │ │ sread (but,drive,1,sectbgn); │ │ for (j=0; j++){ │ printf("%04x": ",j*16); │ for (I=0; I< 16; I++) │ printf("%02x": ",buf[j*16+i]); │ printf(" "); │ for (i=0; I< 16; i++){ │ if (isprint(buf[j+1])) │ putchar(buf[j*16+i]); │ else │ putchar('.'); │ } │ printf("\n"); │ } │} └─────────────────────────┘ ┌──────────────────────────────────┐ │FILE: SSUB.C │ │sread (buf, drive, sectnum, sectbgn) │char *buf; │unsigned int drive, sectnum, sectbgn; │{ │ printf("%d %c %d %d %d\n" ,buf, *buf, drive, sectbgn, sectnum); │} └──────────────────────────────────┘ ;Static Name Aliases ; $25_buf EQU buf TITLE sread .287 _TEXT SEGMENT BYTE PUBLIC 'CODE' _TEXT ENDS . . . _DATA ENDS _BSS SEGMENT $S25_BUF DW 0200H DUP(?) _BSS ENDS _TEXT SEGMENT ;Line 5 PUBLIC _main _main PROC NEAR *** 000000 55 push bp *** 000001 8b ec mov bp,sp *** 000003 b8 0a 00 mov ax,10 *** 000006 e8 00 00 call __chkstk[check stack] *** 000009 57 push di *** 00000a 56 push si ; sectbgn =-2 : i =-4 ; j =-6 ; sectnum =-8 drive =-10 . . . ;Line13 *** 000040 ff 76 fe push WORD PRT[bp-2];setbgn *** 000043 b8 01 00 mov ax,1 *** 000046 50 push ax *** 000047 ff 76 f6 push WORD PRT[bp-10] ;drive *** 00004a b8 00 00 mov ax,OFFSET DGROUP:$S25_ BUF *** 00004d 50 push ax *** 00004e+2 e8 00 00 call sread *** 000051 83 c4 08 add sp,8 . . . ;Line 29 *** 00013b 5e pop si *** 00013c 5f pop di *** 00013d 8b e5 mov sp,bp *** 00013f 5d pop bp *** 000140 c3 ret _main ENDP _TEXT ENDS END ; Static Name Allases ; TITLE ssub .287 _TEXT SEGMENT BYTE PUBLIC 'CODE' _TEXT ENDS _DATA SEGMENT WORD PUBLIC 'DATA' _DATA ENDS _CONST SEGMENT WORD PUBLIC 'CONST' _CONST ENDS _BSS SEGMENT WORD PUBLIC 'BSS' _BSS ENDS DGROUP GROUP CONST, _BSS, _DATA ASSUME CS: _TEXT, DS: DGROUP, SS: DGROUP, ES: DGROUP EXTRN _printf:NEAR EXTRN __chkstk:NEAR _DATA SEGMENT $SG DB '%d %c %d %d %d', 0aH, 00H _DATA ENDS _TEXT SEGMENT ; buf = 4 ; drive = 6 ; sectnum = 8 ; sectbgn = 10 ; Line 2 PUBLIC _sread _sread PROC NEAR *** 000000 55 push bp *** 000001 8b ec mov bp,sp *** 000003 33 c0 xor ax,ax *** 000005 e8 00 00 call __chkstk ; Line 5 *** 000008 ff 76 08 push WORD PTR [BP+8] ;sectnum *** 00000b ff 76 0a push WORD PTR [BP+10] ;sectb *** 00000e ff 76 06 push WORD PTR [BP+6] ;drive *** 000011 8b 5e 04 mov bx,[BP+4] ;buf *** 000014 8a 07 mov al,[bx] *** 000016 98 cbw *** 000017 50 push ax *** 000018 53 push bx *** 000019 b8 00 00 mov ax,offset DGROUP:$SG14 *** 00001c 50 push ax *** 00001d e8 00 00 call _printf ; Line 6 *** 000020 8b ec mov bp,sp *** 000022 5d pop bp *** 000023 c3 ret _sread ENDP _TEXT ENDS END [7]マトリックス・ポインタの関数との受け渡し ┌───────────────────────────────┐ │#include │#include │#include │#include │ │main() │{ │ int I, j, m, n; │ double *Dat, *Norm; │ │ printf ("Category: "); scanf ("%d", &m); │ printf ("Total: "); scanf ("%d", &n); │ │ if ((Dat=(double *)malloc(n*m*8) )==0 ) { │ printf ("can't be memory !!\n"); │ sleep(2); │ exit(1); │} │ if ((Norm=(double *)malloc(n*m*8) )==0 ) { │ printf ("can't be memory !!\n"); │ sleep(2); │ exit(1); │} │ /*------- Data matrix: Dat[n][m] -------*/ │ for (I=0; i< n; I++) { │ printf ("#%3d: ", I); │ for (j=0; j< m; j++) { │ scanf ( "%1f", (Dat+i*m+j) );⇒&Dat[i*m+j]でもよい │ } │ } │ printf("---\n"); │ /*------- Normalization -------*/ │ mat_type (Dat, n,m); │ printf ("\n"); │ │ normalize( Dat, Norm, n, m ); │ mat_type (Norm, n,m); │ printf ("\n"); │ │ free(Dat); │ free(Norm); │} │ └───────────────────────────────┘ ┌───────────────────────────────┐ │/*************************************************************/ │normalize (Data, _Norm, n, m) │ double *Data, *_Norm; int n, m; ⇒ Dat[]でもよい │{ │ mat_equ (Norm, Dat, n, m); │} └───────────────────────────────┘

8。Cとアッセンブラの結合

ある種のプログラムは、機械語で組むしかない。例えば、int 25h、int 26h の セクタ・リード/ライトでは、割り込み後、bp が変化してしまう。また、必ず po pf をしなければならない。これをCの int86x() 関数で実現は出来ない。それで、 アッセンブラで書いたプログラムとCプログラムとの結合が必要となる。二つの方 法がある。 [1]アセンブラで作成、リンカーでCプログラムと結合 プロセジャ・ルーチンとする。cs内に展開し、直接nearコールする。link時に 結合する。 ┌─────────────────┐ │ _TEXT segment public 'CODE' │ │ assume cs:_TEXT │ ┌───────────┐ │ │ │ main() │ │ push bp │ │ { │ │ mov pbsp │ │ _sread proc near │→ │ │ │ . │← │ │ │ . │ │ . │ │ { │ │ ret │ └───────────┘ │ _machine endp │ │ _TEXT ends │ │ end │ └─────────────────┘ 関数用ポインタを使って間接コールする事も出来る。 main() { void (*ff)(); ff=(void *()) spred; (*ff)(x,y,z); } 或は、ポインタ関数を使う事も出来る。 main() { dd (void *()) spread; } dd(void *ff() ) { ff(x,y,z); } 例: セクタ・リード/ライト関数 ; sread (buf, drive, sectnum, sectbgn) ; swrite (buf, drive, sectnum, sectbgn) _TEXT segment byte public 'CODE' assume cs:_TEXT public _sread, _swrite _sread proc near push bp mov bp,sp mov bx,[BP+4] ;buf mov ax,[BP+6] ;drive mov cx,[BP+8] ;sectnum mov dx,[BP+10] ;sectbgn push bp ;bp save int 25h ;int25 use bp popf pop bp ;bp retriev mov sp,bp pop bp ret _sread endp _swrite proc near push bp mov bp,sp mov bx,[BP+4] mov ax,[BP+6] mov cx,[BP+8] mov dx,[BP+10] push bp int 26h popf pop bp mov sp,bp pop bp ret _swrite endp _TEXT ends end [2]プログラムに機械語関数を埋め込む 短い機械語プログラムの時、その機械語コードを、データ・アレイとして、Cプ ログラム内に記述する。データ・アレイは、ds 内に展開される。それを、「関数 の far ポインタ」でコールが必要。しかも、データ・アレイとして宣言するので、 直接コールでは引数定義できない。 それで、関数ポインタによる間接コールが必要となる。ポインタによる間接コー ルは、次のようにする far 関数の宣言: 型指定子 far 関数名(); far ポインタの宣言: 型指定子 (far *ポインタ名)(); far ポインタによるfar 関数先頭指定: ポインタ名=( void (far *)() ) 関数名; far 関数の実行: ( *ポインタ名)(); code segment data segment ┌──────────────────┐┌─────────────┐ │ void far machine[]; ││ │ │ main() { ││ machine[ ] │ │ void (far *secred)(); ││┌──────┐push bp │ │ secread=(void (far *)())machine;│││ 0x55 │mov pb,sp │ │ │││ 0x8b, 0xec│ │ │ │││ . │ │ │ (*secread)("x"); │→│ . │ │ │ │←│ . │ │ │ } │││ 0xcb │ret │ │ ││└──────┘ │ └──────────────────┘└─────────────┘ 或は、次のようにポインタ関数ddを宣言してもよい。 code segment data segment ┌────────────────┐ ┌────────────┐ │ void far machine[]; │ │ │ │ main() { │ │ │ │ dd( (void (far *)())machine );│ │ machine[ ] │ │ } │ │ │ │ void (far *secred)(); │ │┌─────┐push bp │ │ dd(secread); │ ││ 0x55 │mov pb,sp │ │ void (far *secread)(); │ ││ 0x8b,0xec│ │ │ │ ││ . │ │ │ (*secread)("x"); │ ││ . │ │ │ } │ ││ 0xcb │ret │ │ │ │└─────┘ │ └────────────────┘ └────────────┘ 例: セクタ・リード #include #include #define SECTBYTE 1024 main() { void (far *secread)(); int I, j, drive, sectbgn; unsigned char buf[SECTBYTE]; static char machin[]={ 0x55, /* push bp 0x8b, 0xec /* mov bp,sp 0x8b, 0x5e, 0x06, /* mov bx,[bp+6] 0x8b, 0x46, 0x08, /* mov ax,[bp+8] 0x8b, 0x4e, 0x0a, /* mov cx,[bp+10] 0x8b, 0x56, 0x0c, /* mov dx,[bp+12] 0xcd, 0x25, /* int 25h 0x9d, /* popf 0x5d, /* pop bp 0xcb, /* retf }; secread=(void (far *)())machine; printf("DRIVE= ")scanf("%d", &drive); printf("SECTBGN= "); scanf("%d", §bgn); buf[0]='Z'; (*secread) (buf, drive, 1, sectbgn); for (j=0; j9.プログラム間ジャンプ (a) プログラム間ジャンプ 関数内のジャンプ goto で行う。数外へのジャンプは setjmp(env) / longjmp(env, value) の命令対により行う。 setjmp (env) を実行すると、現在のスタック情報をenv バッファに セットし、無条件に0(偽)を返す。longjmp(env, value) が実 行されると、setjmp 時にセ−ブした env 情報をリストアし、setjmp 位置にジャンプする。このとき、setjmp はあたかも value の値を返すよ うに働く。 ■setjmp / longjmp を用いたエラ−修復機構 COPY コマンドなどでは、ファイル名を間違える等によって、ファイル・オ− プンできない時、OS に戻るようになっている。ここでは、もしファイル・オ−プ ンできなければ再度ファイル名を入力し、最初からやり直すことが出来るプログラ ムを作ります。 #include #include jmp_buf env; main(int argc,char *argv[]) { int err; char fname[64] if (err==setjmp(env)) { switch (err) { case 1: printf("Source filename ???\n"); printf("Input file name ? "); scanf("%s",fname); argv[1]=fname; break; case 2: printf("Destinate filename ???\n"); printf("Input file name ? "); scanf("%s",fname); argv[2]=fname; } } copy(argv[1], argv[2]); } copy (char fname1, char *fname2) { FILE *fp1,*fp2; int c; if ((fp1=fopen(fname1,"rb"))==0) longjmp(env,1); if ((fp2=fopen(fname2,"wb"))==0) longjmp(env,2); while ((c=getc(fp1))!=EOF) putc(c,fp2); fclose(fp1); fclose(fp2); }

10.プロセスのリンク

[1] プロセス プロセスはマルチタスクのOS上の概念。 MS_DOSで、プロセスとは、メモリにロ−ドされ、実行中のプログラム。 1つのプロセス(親プロセス)から、別のプログラム(子プロセス)をロ−ド、 実行出来る。子プロセスから、さらに子プロセスを起動できる。 a.親プロセスから、子プロセスの起動 exec : 子プロセスを親プロセスにオ−バ・レイ(子から親に戻れない)。 spawn: 親プロセスに戻ることが出来る。 system : MS-DOS コマンドを子プロセスとして実行し、親プロセスに戻る。 この時、親プロセスの環境は、命令パラメータによって、子プロセスに引き継 がれる。 b.子から親プロセスへの戻り exit$abort これらの命令は、親プロセスへの「リタ−ン・コ−ド」を持つ。 コマンドラインによって、プログラムを実行したとき、親プロセスは、 MS_DOS(COMMAND.COM)。 c.バッチ・ファイル: 「リタ−ン・コ−ド」を得るには、’if errorlevel n〜’文 を使いる。これはリタ−ン・コ−ドが n 以上なら〜せよと言う意味。 次の例は、 プロセス(recode) -> プロセス(child) の順に実行。 CHILD 内のリタ−ン・コ−ド <ret> は、親プロセスに返す。 /******** バッチ・ファイルtest.bat ***********************/ B:retcode if errorlevel 1 dir b:/w /******* プロセスretcode ********************************/ #include #include main() { int ret; ret=spawnlp(P_WAIT, "child.exe", "", NULL); if (ret==-1) { printf("exec error\n"); exit(1); } printf("child process ret-code=%d\n", ret); return(ret); } /********* プロセスchild ********************************/ #include main() { int ret; printf("input retcode --> "); scanf("%d",&ret); exit(ret); } [2] マクロ・アセンブラ開発支援ツ−ル プロセスを利用すれば、メインプログラムからエディタ、アセンブラ、リンカを 呼び出すことが出来る。 そこで、 A>PROCESS ABC と起動することにより、ABC.asm のエディト、アセンブル/リンク、及び ABC.exe の実行、デバッグを、メニュ−選択で行うことが出来る。従って、アセンブルで エラ−が生じれば、すぐエディタを起動して修正し、再びアセンブルするというこ とが簡単に行える。 #include #include main(argc,argv) int argc char *argv[]; { int ret,c; static char fname[4][64]; strcpy(fname[0],argv[1];strcat(fname[0],".asm"); strcpy(fname[1],argv[1];strcat(fname[1],";"); strcpy(fname[2],argv[1];strcat(fname[2],".exe"); while ((c=menu())!='6'){ switch (c){ case '1': spawnlp(P_WAIT,"wm","wm",fname[0],NULL); break; case '2': ret=spawnlp(P_WAIT,"masm","masm",fname[1],NUL); if (ret==0){ sapwnlp(P_WAIT,"link","link",fname[1},NULL); } break; case '3': spawnlp(P_WAIT,argv[1],argv[1],NULL); break; case '4': spawnlp(P_WAIT,"symdeb","symdeb",fname[2],NUL); break; case '5': exit(); } } } menu() { int c; printf("1: edit\n"); printf("2: assemble\n"); printf("3: excute\n"); printf("4: debug\n"); printf("5: exit\n"); printf("number --> "); c=getchar(); rewind(stdin); return(c); } このプログラムでは、エディタやアセンブラを自前で作らず、既存のものを spawn で呼び出して利用している。 MASM を spawan で起動すると、そのリタ−ン・コードが戻り値になる。この値 をみて、LINKを行うかを決められる。MASM は,エラ−がなければ 0 を返す。なお、 リターン・コードは MASM のバ−ジョンにより異なる。 なお、MSC コンパイラ (msc.exe) のようなオ−バ・レイを行うプログラム は、spawn では呼び出せない(execは、出来る)。このプログラムの起動には、 process をそのまま使えない。 [3] 子プロセスの環境 MS-DOS における一つのプロセスに割り当てられるメモリ・ブロックの構成 は、以下のようになっています。 ...................... 子プロセス開始時、親プロセスの「環境テ−ブル」の内容は,、プロセスのPSP の先頭にコピ−される。(putenv() getenv() でこの環境テ−ブルの内容を参 照出来る)。 親プロセスと異なる環境での、子プロセスの起動には、 spawnle, spanwnve, execle, execve を用いる。この時、ファイルは、カレント・ディレクトリ内しか探せない。 (pathに従って探すことは出来ない。) exec: 親の環境を変更してよい場合あり。このよう PATH にしたがって ファイルを探すため、putenv により親の環境テ−ブルを子プロセス用に変更 した後、execlp, execvp により起動を行なる場合もある。 ■異なる環境でのコンパイル/リンク MSC でコンパイル/リンクするとき、INCLUDE や LIB といった環境 変数で指定されたディレクトリからインクル−ド・ファイルやライブラリを探す。 時として異なる環境のディレクトリから、これらを探したいときがある。このと き、SET コマンドでいちいち環境を変更することは煩雑です。そこで異なる環境 でのコンパイル/リンクを行う cl コマンドを作ると便利です。 /***** 異なる環境でのコンパイル/リンクをするclコマンド *****/ #include #include main(int argc, char *argv[]) { putenv("INCLUDE=B:\\"); putenv("LIB=B:\\LIB"); printf("以下の環境でコンパイルします¥n"); printf ( "INCLUDE=%s\n", getenv("INCLUDE") ); printf ( "LIB=%s\n", getenv("LIB") 1); execvp ("cl",argv); } A>b:env q.c 以下の環境でコンパイルします INCLUDE=B:\ LIB=B:\LIB Microsoft (R) C Compailer Version 3.00.17 Copyright (C) Microsoft Corp 1984. 1985. All rights reserved MS-DOS のコマンド・ラインでは、小文字は大文字に変換される。SET で設 定された文字列は大文字で入っている。一方、Cでは、小文字と大文字は区別され るので、iclude とINCLUDE は異なるものになる。特別な場合を除い て大文字で書いておく方が安全。