マクロ展開 - e-publishing.jp会社ロゴ

■マクロ展開(平成 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