■マクロ展開(平成 14 年春午後問 10) 次のCプログラムの説明及びプログラムを読んで、設問に答えよ。 〔プログラム〕 #include <stdio.h> #include <string.h> 〔プログラムの説明〕 マクロ機能をもつあるプログラム言語で書かれたプログラムが、元ファイル source_file に格納されている。 この source_file 中のマクロ名を展開し、展開ファイル dest_file を生成する。 (1) 元ファイルには、マクロ定義とマクロ名を使用するプログラムのコードが格納されている。 (2) 展開ファイルには、マクロ名を展開したプログラムのコードを出力する。 ただし、元ファイルのマクロ定義は、出力しない。 (3) マクロ定義は、次の形式とする。 $STRDEF マクロ名 展開コード $STRDEF は、必ず1けた目から始まり、$STRDEF とマクロ名の間、及びマクロ名と展開コードの間は、 一つの空白文字で区切られる。 (4) 一つのマクロ名に対するマクロ定義は、1回だけ行うことができる。 (5) 元ファイル中で、マクロ定義以降のコードに対して、区切り文字で区切られたマクロ名の部分を 展開コードに置換する。 ただし、マクロ定義の行は、置換の対象とはしない。 (6) 区切り文字は、次の ① ∼ ③ であり、文字型の配列 delmchar に格納されている。 ① 空白文字 ② 図形文字 !"#%&'()*+,-./:;<=>?[\]^_{|}~ ③ 改行文字 char delmchar[ ] = " !\"#%&'()*+,-./:;<=>?[\\]^_{|}~\n"; 注 delmchar の先頭に要素には、空白文字が格納されている。 (7) マクロ名及び展開コードは、区切り文字を含まない。 (8) 元ファイル中に現れるマクロ定義の個数は、20 までとする。 (9) マクロ名及び展開コードは、31 文字までとする。 (10)展開の前も後も、各行は改行文字で終わり、文字数は 255 文字までとする。 (11)プログラム中で使用している関数 strchr の仕様は、次のとおりである。 #define MCRDEF #define STRLNG #define LINLNG 20 32 256 int divide_line(); char linbuf[LINLNG], token[LINLNG][LINLNG], delm[LINLNG]; char label[] = "$STRDEF"; char delmchar[] = " !\"#%&'()*+,-./:;<=>?[\\]^_{|}~\n"; main() { FILE *sfp, *dfp; int mcrcnt = 0, tokcnt, flg, idx1, idx2; char orig[MCRDEF][STRLNG], expand[MCRDEF][STRLNG]; sfp = fopen("source_file", "r"); dfp = fopen("dest_file", "w"); while(fgets(linbuf, LINLNG, sfp) != NULL) { tokcnt = divide_line(); if(strcmp(token[0], label) == 0) { strcpy(orig[mcrcnt], token[1]); strcpy(expand[mcrcnt], token[2]); a ; } else { for(idx1 = 0; idx1 < b ; idx1++) { flg = 0; for(idx2 = 0; idx2 < mcrcnt; idx2++) if(strcmp(token[idx1], orig[idx2]) == 0) { flg = 1; break; } char *strchr(const char *s, int c) 機能: s で指定される文字列の中の c(char 型に変換する。)の最初の出現を捜す。 文字列の終端を示すナル文字は、文字列の一部とみなす。 返却値:捜し出した文字へのポインタを返す。 文字列の中に、その文字が現れない場合、空ポインタ(NULL)を返す。 (12)このプログラムの実行例は、次のとおりである。 c if( ) fprintf(dfp, "%s%c", token[idx1], delm[idx1]); 元ファイル ��������������������� �������������������� ���������������� ������������� ������������������������� �������������������������� �������������������������� ������������������������ ������������ ������������������������ ����������������������� ���������������������� ����������� 展開ファイル else fprintf(dfp, "%s%c", expand[idx2], delm[idx1]); ������������� ����������������������� ��������������������� ��������������������� ����������������������� ������������ ������������������������ ����������������������� ����������������� ����������� } } } fclose(sfp); fclose(dfp); } 実行例 www.e-publishing.jp int divide_line() { int tokcnt = 0, lidx = 0, tidx; do { for(tidx = 0; strchr(delmchar, linbuf[lidx]) == NULL; lidx++, tidx++) token[tokcnt][tidx] = linbuf[lidx]; d token[tokcnt][tidx] = delm[tokcnt] = f e ; fgets:file get string /******* fgets.c *******/ #include <stdio.h> #define LINLNG 256 /* 各行の最大文字数 */ ; ; tokcnt++; } while(linbuf[lidx - 1] != '\n'); void print_line(); char linbuf[LINLNG]; return tokcnt; } 設問 プログラム中の に入れる正しい答えを、解答群から選べ。 aに関する解答群 ア idx1 = 0 イ idx1++ ウ idx2 = 0 エ idx2++ オ mcrcnt = 0 カ mcrcnt++ キ tokcnt = 0 ク tokcnt++ bに関する解答群 ア idx2 イ idx2++ ウ mcrcnt エ mcrcnt++ オ tokcnt カ tokcnt++ d、eに関する解答群 ア '\0' イ '\n' エ delm[tidx] オ linbuf[lidx] キ token[lidx] ク token[tidx] main() { FILE *sfp; sfp = fopen("a:source_file.dat", "r"); while( fgets(linbuf, LINLNG, sfp) != NULL ) { /* linbuf に source_file.dat の一行を読込む処理を、ファイル末まで続ける */ print_line(); } printf("\n"); fclose(sfp); } void print_line() { printf("linbuf: %s", linbuf); } /* linbuf に格納した一行分を出力する */ 問題文の実行例にあるような内容の元ファイル(source_file.dat)を用意し、実行した場合を示す。 【実行結果】 cに関する解答群 ア flg = 0 イ flg == 0 ウ flg = 1 エ flg == 1 オ idx2 = 0 カ idx2 > 0 キ idx2 < mcrcnt ク idx2 = mcrcnt fに関する解答群 ア lidx = 0 イ lidx++ エ tidx++ オ tokcnt = 0 ■平成 14 年春午後問 10 解答例 当該プログラムでは各種の関数が使われており、これらの関数の機能を知らずして、プログラム全体の動作を 正確に理解することはできない。 従って、ここでは、まず、当該プログラムで使われている関数のうち、fgets (file get string) と、 strchr (string character) の動作についてみておこう。 ウ delm[lidx] カ linbuf[tidx] linbuf: linbuf: linbuf: linbuf: linbuf: linbuf: linbuf: linbuf: linbuf: linbuf: linbuf: linbuf: linbuf: linbuf: $STRDEF $$TITLE HELLO $STRDEF $$SIGN BILLY $STRDEF $$LENG 8 START OUT IN OUT OUT EXIT $$TITLE DC $$SIGN DC BUF DS END fgets(linbuf, LINLNG, sfp) ・linbuf: 入力文字列の格納先 ・LINLNG: 格納先の大きさ(バイト) ・sfp: 入力ストリーム MSG $$TITLE,6 BUF,$$LENG BUF,$$LENG $$SIGN,6 'Hello!' 'Billy' $$LENG ウ tidx = 0 カ tokcnt++ www.e-publishing.jp strchr:string character 問題のプログラムの処理を、大まかにみていこう。 /******* strchr.c *******/ #include <stdio.h> #include <string.h> /* strchr */ sfp = fopen("source_file", "r"); :元ファイルからデータを読み込む。 main() { char delmchar[] = " !\"#%&'()*+,-./:;<=>?[\\]^_{|}~\n"; char *pchr; int c; printf(" 文字を入力してください。"); c = getchar(); pchr = strchr(delmchar, c); printf("%s\n", pchr); } tokcnt = divide_line(); :関数 divide_line() を呼び出し、その返却値を tokcnt とする。 int divide_line() { :一行分のコード分割処理を行う。 do { for( tidx=0; strchr(delmchar, linbuf[lidx]) == NULL; lidx++, tidx++ ) token[tokcnt][tidx] = linbuf[lidx]; :配列 delmchar に格納された文字列の中に、linbuf[lidx] で示す文字が存在しないとき、 strchr(delmchar, linbuf[lidx]) は NULL を返す。 従って、for ループを抜けるのは、配列 delmchar に格納された文字列の中に、linbuf[lidx] で示す 文字が存在するときである。 つまり、linbuf[lidx] の文字が、配列 delmchar に格納された「区切り文字」でない間は、 token[tokcnt][tidx] に、linbuf[lidx] の文字を一文字ずつ格納していく。 【実行結果】 文字を入力してください。+ +,-./:;<=>?[\]^_{|}~ 文字を入力してください。? ?[\]^_{|}~ 文字を入力してください。1 (null) while( fgets(linbuf, LINLNG, sfp) != NULL ) :linbuf にデータを一行分読み込む。 この処理を NULL でない間、つまり、ファイル末まで続ける。 注意:制御文字は表示されない。 「+」と入力すると、delmchar[] = " !\"#%&'()*+,-./:;<=>?[\\]^_{|}~\n"; のうち、 「+」以後の文字列が表示される。 delmchar[] = " !\"#%&'()*+,-./:;<=>?[\\]^_{|}~\n"; に無い文字「1」を入力すると、 null が返される。 for ループを抜けると、空欄を有する各種の処理が行われる。 for ループを抜けるということは、linbuf[lidx] が「区切り文字」であったということである。 つまり、空欄d、e、fは、「区切り文字」を検出した後の処理である。 d token[tokcnt][tidx] = ; :直前の for ループによって、token[tokcnt][tidx] には文字群が格納されている。 その文字群を、main 関数側で、token[0]、token[1]、token[2]、token[idx1] に格納された文字列 として参照できるためには、文字群の末尾に「'\0'」を付加しておく必要がある。 空欄dの答え:ア delm[tokcnt] = e ; :delm[tokcnt] には、検出した「区切り文字」を格納する必要がある。 区切り文字が格納されているのは、linbuf[lidx] である。 空欄eの答え:オ f ; tokcnt++; :配列 linbuf[lidx] と配列 token[tokcnt] の要素番号のコマを、共に進める必要がある。 空欄fの答え:イ } while( linbuf[lidx-1] != '\n' ); :do ∼ while 間の処理を、一行について、改行を検出するまで続ける。 return tokcnt; :一行のコード数を返す。 www.e-publishing.jp 具体的にプログラム処理をみてみよう。 下記のプログラムは、問題文のプログラムを改変し、処理結果をディスプレイに表示するようにしたものである。 また、本質をみるため、元ファイル(source_file.dat)も次に示すような簡易なものに変更してある。 int divide_line() { int tokcnt = 0, lidx = 0, tidx; do { $STRDEF $$TITLE HELLO OUT $$TITLE,6 for( tidx=0; strchr(delmchar, linbuf[lidx]) == NULL; lidx++, tidx++ ) token[tokcnt][tidx] = linbuf[lidx]; /******* H14haru10-01.c *******/ #include <stdio.h> #include <string.h> /* strchr */ #define MCRDEF #define STRLNG #define LINLNG int char char char 20 32 256 token[tokcnt][tidx] = '\0'; delm[tokcnt] = linbuf[lidx]; printf("token[%2d]: %8s /* 元ファイル中のマクロ定義最大個数 */ /* マクロ名、展開コードの最大文字数 */ /* 各行の最大文字数 */ divide_line(); linbuf[LINLNG], token[LINLNG][LINLNG], delm[LINLNG]; label[] = "$STRDEF"; delmchar[] = " !\"#%&'()*+,-./:;<=>?[\\]^_{|}~\n"; lidx++; /* string definition */ /* 区切り文字 */ main() { FILE *sfp; int mcrcnt = 0, tokcnt, flg, idx1, idx2; char orig[MCRDEF][STRLNG], expand[MCRDEF][STRLNG]; sfp = fopen("source_file.dat", "r"); /* line index */ tokcnt++; } while( linbuf[lidx-1] != '\n' ); /* token counter */ return tokcnt; /* 一行中のコード数を返す */ } � � � � � � � � � � � � � � � � � �� �� �� �� �� �� �� �� �� �� �� �� �� �� �� � � � � � � � � � � � � � � � �� � � � � � � � � �� � �� /* 元ファイル */ while( fgets(linbuf, LINLNG, sfp) != NULL ) { /* linbuf に source_file の1行を読込む処理を、ファイル末まで続ける */ tokcnt = divide_line(); if( strcmp(token[0], label) == 0 ) { /* token[0] と label=="$STRDEF" が等しければ */ strcpy(orig[mcrcnt], token[1]); /* orig[mcrcnt] に token[1]: マクロ名をコピー */ strcpy(expand[mcrcnt], token[2]); /* expand[mcrcnt] に token[2]: 展開コードをコピー */ mcrcnt++; /* 空欄a */ } else { /* マクロ定義以外の行に関する処理 */ for( idx1=0; idx1<tokcnt; idx1++ ) { /* idx1 < 空欄b */ flg = 0; for( idx2=0; idx2<mcrcnt; idx2++ ) if( strcmp(token[idx1], orig[idx2]) == 0 ) { flg = 1; break; } if( flg==0 ) /* if( 空欄c ) */ printf("%s%c", token[idx1], delm[idx1]); /* printf に変更 */ else printf("%s%c", expand[idx2], delm[idx1]); /* printf に変更 */ } } } fclose(sfp); } delm[%2d]: %c\n", tokcnt, token[tokcnt], tokcnt, delm[tokcnt]); 【実行結果】 token[ 0]: token[ 1]: token[ 2]: token[ 0]: token[ 1]: token[ 2]: $STRDEF $$TITLE HELLO ……………… token[ 7]: token[ 8]: OUT token[ 9]: delm[ 0]: delm[ 1]: delm[ 2]: delm[ 0]: delm[ 1]: delm[ 2]: delm[ 7]: delm[ 8]: delm[ 9]: token[10]: token[11]: token[12]: token[13]: token[14]: OUT token[ 0]: $$TITLE 6 delm[10]: delm[11]: delm[12]: delm[13]: , delm[14]: HELLO,6 delm[ 0]: 「………………」の部分は省略を示し、 実際には、token[3], delm[3] ∼ token[6], delm[6] は全て空白が出力される。 空欄a 配列 orig[mcrcnt] と配列 expand[mcrcnt] について、共に、次の格納領域に要素番号のコマを進める必要 があるので、mcrcnt++ を入れる。 空欄aの答え:カ 空欄b マクロ定義以外の行について、コードの処理個数を規定する部分である。 一行中のコード数は tokcnt である。 空欄bの答え:オ 空欄c if( strcmp(token[idx1], orig[idx2]) == 0 ) により、マクロ名と一致するとき、つまり、マクロ展 開するとき、flg=1 としている。空欄cを含む if ∼ else 文をみると、else の場合、配列 expand を出力し、 マクロ展開している。 従って、空欄cはマクロ展開しない場合: flg==0 である。 空欄cの答え:イ 空欄dの答え:ア 空欄eの答え:オ 空欄fの答え:イ www.e-publishing.jp /******* /******* #include #include #define MCRDEF #define STRLNG #define LINLNG int char char char int divide_line() { /* 一行に関するコード分割処理 */ int tokcnt = 0, lidx = 0, tidx; H14haru10-02.c *******/ マクロ展開 *******/ <stdio.h> /* fgets, fopen, fclose, fprintf */ <string.h> /* strcmp, strcpy, strchr */ 20 32 256 do { /*** 区切り文字以外のとき ***/ for( tidx=0; strchr(delmchar, linbuf[lidx]) == NULL; lidx++, tidx++ ) token[tokcnt][tidx] = linbuf[lidx]; /*** 区切り文字が出現したとき ***/ token[tokcnt][tidx] = '\0'; delm[tokcnt] = linbuf[lidx]; lidx++; /* line index */ tokcnt++; /* token counter */ } while( linbuf[lidx-1] != '\n' ); /* 一行に関して、改行を検出するまで繰り返す */ /* 元ファイル中のマクロ定義最大個数 */ /* マクロ名、展開コードの最大文字数 */ /* 各行の最大文字数 */ divide_line(); linbuf[LINLNG], token[LINLNG][LINLNG], delm[LINLNG]; label[] = "$STRDEF"; delmchar[] = " !\"#%&'()*+,-./:;<=>?[\\]^_{|}~\n"; /* line buffer, トークン , delimit */ /* string definition */ main() { return tokcnt; FILE *sfp, *dfp; int mcrcnt = 0, tokcnt, flg, idx1, idx2; char orig[MCRDEF][STRLNG], expand[MCRDEF][STRLNG]; sfp = fopen("source_file.dat", "r"); dfp = fopen("dest_file", "w"); /* macro counter */ /* original expandable */ /* 元ファイル */ /* 展開ファイル */ while( fgets(linbuf, LINLNG, sfp) != NULL ) { /* linbuf に source_file の一行を読込む処理を、ファイル末まで続ける */ tokcnt = divide_line(); /*** マクロ定義の行に関する処理 ***/ if( strcmp(token[0], label) == 0 ) { /* token[0] と label=="$STRDEF" が等しければ */ strcpy(orig[mcrcnt], token[1]); /* orig[mcrcnt] に token[1]: マクロ名をコピー */ strcpy(expand[mcrcnt], token[2]); /* expand[mcrcnt] に token[2]: 展開コードをコピー */ mcrcnt++; /* orig[],expamd[] の要素番号のコマを進める */ } else { /*** マクロ定義以外の行に関する処理 ***/ for( idx1=0; idx1<tokcnt; idx1++ ) { flg = 0; for( idx2=0; idx2<mcrcnt; idx2++ ) if( strcmp(token[idx1], orig[idx2]) == 0 ) { /* token[] がマクロ名なら */ flg = 1; break; } if( flg==0 ) /* マクロ名以外のコードのとき */ fprintf(dfp, "%s%c", token[idx1], delm[idx1]); /* マクロ名以外のコードをそのまま出力した後、区切り文字を出力 */ else /* flg==1: マクロ名のとき */ fprintf(dfp, "%s%c", expand[idx2], delm[idx1]); /* マクロ展開出力した後、区切り文字を出力 */ } } } fclose(sfp); fclose(dfp); } /* 一行中のコード数を返す */ } 問題文の実行例にあるような元ファイル:source_file.dat を作成し、プログラムを実行した場合を示す。 元ファイル:source_file.dat $STRDEF $$TITLE HELLO $STRDEF $$SIGN BILLY $STRDEF $$LENG 8 START OUT IN OUT OUT EXIT $$TITLE DC $$SIGN DC BUF DS END 【実行結果】 展開ファイル:dest_file MSG MSG $$TITLE,6 BUF,$$LENG BUF,$$LENG $$SIGN,6 'Hello!' 'Billy' $$LENG HELLO BILLY BUF START OUT IN OUT OUT EXIT DC DC DS END HELLO,6 BUF,8 BUF,8 BILLY,6 'Hello!' 'Billy' 8 注意:END の直後には必ず改行を入れる。 【課題】 配列 delmchar[] に、一文字を加えることによって、元ファイルと同じものを、dest_file として出力 させなさい。 (ヒント:元ファイルのマクロ名に関する行を、通常の行として、プログラムに認識させる。) www.e-publishing.jp
© Copyright 2025 ExpyDoc