基本情報技術者 2009年 春期 午後 問09
次のCプログラムの説明及びプログラムを読んで、設問1,2に答えよ。
〔プログラムの説明〕
与えられたパスを絶対パスに変換する関数convertである。
階層構造をもつファイルシステムにおいて、ファイルやディレクトリを特定する文字列をパスという。ルートディレクトリを基準としたパスを絶対パスと呼び、“/”から始まり、各階層を“/”で区切っていく。与えられたパスがディレクトリのとき、最後の“/”はあってもなくてもよい。例えば、図のディレクトリeの絶対パスは“/a/d/e”又は“/a/d/e/”で示す。
一方、カレントディレクトリを基準としたパスを相対パスと呼び、相対パスを指定するときに階層を一つ上にたどる場合は“..”を用いる。例えば、図においてディレクトリcをカレントディレクトリにした場合、ファイルfile1.txtの相対パスは“../file1.txt”、ディレクトリeの相対パスは“../../d/e”又は“../../d/e/”となる。また、カレントディレクトリ自身は“.”又は“./”で示す。

(1) 関数の仕様は、次のとおりである。
void convert(const char *path, const char *base, char *result);
引数:path 変換前のパス
base カレントディレクトリの絶対パス
result 変換後の絶対パス
機能:pathが相対パス表記であれば、baseを基準にした絶対パス表記に変換し、resultに格納する。pathが絶対パス表記であれば、resultにはbaseに関係なくpathをそのまま格納する。
返却値:なし。
ただし、resultが参照する領域は、変換後の文字列を格納するのに十分であるとする。また、冗長なパス又はパスとして認識できない文字列が引数として与えられることはないものとする。
(2) ファイルシステム上に、指定されたディレクトリやファイルが実際に存在するかどうかのチェックは行わない。
(3) 変換例を表1に示す。

(4) 次のライブラリ関数を用いる。
unsigned int strlen(const char *s);
機能:文字列sの長さを計算する。
返却値:終端を示すナル文字に先行する文字の個数を返す。
int strcmp(const char *s1, const char *s2);
機能:文字列s1と文字列s2を比較する。
返却値:s1とs2が同一文字列の場合は0、それ以外の場合は0以外を返す。
int strncmp(const char *s1, const char *s2, int n);
機能:文字列s1と文字列s2を先頭からn文字、又はナル文字までを比較する。
返却値:比較したn文字が同一の場合は0を、それ以外の場合(比較が途中で終了した場合も含む)は0以外を返す。
char *strcpy(char *s1, const char *s2);
機能:文字列s1に文字列s2をナル文字まで複写する。
返却値:s1
char *strncpy(char *s1, const char *s2, int n);
機能:文字列s1に文字列s2をn文字複写する。s2の長さがn以上の場合はn文字目までを複写し、n未満の場合は残りをナル文字で埋める。
返却値:s1

設問1:
問題文を見る プログラム中のに入れる正しい答えを、解答群の中から選べ。
a, bに関する解答群
ア:strcpy(base, path)
イ:strcpy(base, result)
ウ:strcpy(path, base)
エ:strcpy(path, result)
オ:strcpy(result, base)
カ:strcpy(result, path)
cに関する解答群
ア:bp - base
イ:bp - path
ウ:pp - base
エ:pp - path
dに関する解答群
ア:base + length
イ:bp + length
ウ:path + length
エ:pp + length
オ:result + length
模範解答
a:カ、b:オ、c:ア、d:オ
解説
解答の導き方
空欄は、まず絶対パスとカレントディレクトリの場合を考え、その後、相対パスを組み立てる処理を追うと判断できます。
1. pathが絶対パスの場合
本文には、pathが絶対パス表記なら「baseに関係なくpathをそのまま格納する」とあります。結果を入れる先はresultなので、pathの内容をresultへコピーします。
したがって、aには「strcpy(result, path)」が入り、解答群ではカです。
2. pathがカレントディレクトリの場合
pathが「.」又は「./」なら、表1のように変換結果はカレントディレクトリの絶対パスになります。この絶対パスは引数baseです。そのため、baseをresultへコピーします。
したがって、bには「strcpy(result, base)」が入り、解答群ではオです。
3. 相対パスを組み立てる処理
ここからは、変数の役割を押さえます。
- bpは、baseのうちresultにコピーする範囲の終わりを示します。bpはポインタ(文字列の中の位置を指すもの)です。
- ppは、pathのうち先頭の「../」や「./」を取り除いた後の位置を示します。
- resultには、baseの必要な部分とpathの残りをつないだ文字列を作ります。
例として、表1のpathが「../../b/c/」、baseが「/a/d/e/」の場合を追います。baseの末尾に「/」があるので、最初にbpをその末尾の「/」へ移します。その後、「../」を一つ処理するたびに、bpをbaseの中で一つ上の階層へ戻します。
この例では、最後にbaseから使う部分は「/a」で、その文字数は2文字です。bpとbaseはどちらもbase内の位置を指しているため、「bp - base」は、baseの先頭からbpまでの文字数になります。
したがって、cには「bp - base」が入り、解答群ではアです。この値を使ってstrncpyでbaseの先頭部分をresultへコピーします。
4. resultの続きにpathをつなぐ
strncpyでbaseの先頭部分をコピーした後、その部分の終わりに「/」を追加します。追加位置は、resultの先頭からlength文字進んだ位置です。
つまり、rpをresultの先頭からlength文字進めた位置にすればよいので、dには「result + length」が入り、解答群ではオです。
その位置に「/」を置いた後、strcpyでppが指すpathの残りを続けてコピーします。先ほどの例なら、「/a」の後に「/」と「b/c/」がつながり、結果は「/a/b/c/」になります。
誤りやすいポイント
- strcpyの引数は、コピー先が先、コピー元が後です。絶対パスの場合はresultがコピー先なので、pathからresultへコピーします。
- pathが「.」又は「./」の場合、resultに入れるのはpathそのものではありません。カレントディレクトリの絶対パスを表すbaseをコピーします。
- cで使うのは「pp - path」ではありません。これはpathの中で何文字進んだかを表します。ここで必要なのは、baseから何文字コピーするかなので「bp - base」です。
- dでは、コピー先resultの中で続きの文字を書き始める位置を求めます。そのため「result + length」を使います。
FAQ
Q: lengthは、なぜpathの長さではなく「bp - base」なのですか?
A: lengthは、baseからresultへコピーする文字数だからです。pathの先頭部分はppが指す位置まで読み飛ばされており、その残りを別に追加します。
A: lengthは、baseからresultへコピーする文字数だからです。pathの先頭部分はppが指す位置まで読み飛ばされており、その残りを別に追加します。
Q: 「./」を処理するとき、bpが動かないのはなぜですか?
A: 「./」は現在の階層を表すため、baseを一つ上の階層へ戻す必要がありません。path側のppだけを進めます。
A: 「./」は現在の階層を表すため、baseを一つ上の階層へ戻す必要がありません。path側のppだけを進めます。
Q: コピーした文字列の後に、なぜ「/」を追加するのですか?
A: baseの必要な部分とpathの残りを、階層の区切りでつなぐためです。その後にpathの残りをコピーすることで、ひと続きの絶対パスになります。
A: baseの必要な部分とpathの残りを、階層の区切りでつなぐためです。その後にpathの残りをコピーすることで、ひと続きの絶対パスになります。
関連キーワード: 絶対パス、相対パス、ポインタ、文字列コピー、パスの正規化
設問2:
問題文を見る 表2の引数列で関数convertを呼んだときのプログラムの動作について、表2中のに入れる正しい答えを、解答群の中から選べ。ただし、プログラム中のa〜dには正しい答えが入っているものとする。

eに関する解答群
ア:../d/
イ:../../../../d/
ウ:/
エ:/../d/
オ:/d/
カ:d/
f, gに関する解答群
ア:/a/b/c/d
イ:/a/b/c/d/
ウ:/a/b/cd
エ:/a/b/cd/
オ:d
カ:d/
模範解答
e:オ、f:イ、g:ア
解説
解答の導き方
この設問では、相対パスを絶対パスに変えるとき、baseのどこまでを残し、pathのどの部分を後ろに付けるかを追います。
プログラムでは、pathが絶対パスならそのまま使い、pathが「.」又は「./」ならbaseを使います。今回の3行はどれも相対パスで、カレントディレクトリ自身を表すものでもないため、その後の処理に進みます。
処理では、bpがbaseのうち残す部分の境目を示します。baseの末尾に「/」があれば、先にその分だけbpを戻します。その後、pathの先頭にある「../」の数だけ、bpを一つ上の階層へ戻します。最後に、baseの残す部分、区切りの「/」、pathの残りをつなげます。
eの行:pathが「../../../../d/」、baseが「/a/b/c/」の場合
pathの先頭には「../」が4個あります。「../」は一つ上の階層へ移動する指定です。
baseの末尾の「/」を除くと、出発点は「/a/b/c」です。先頭の「../」を順に処理すると、次のようになります。
4個目の「../」を処理する時点では、すでにルートディレクトリ「/」にいます。プログラムはbpがbaseの先頭より前に進まないようにしているため、ルートより上には進みません。
4個の「../」を取り除いた後に残るpathは「d/」です。baseから残す部分はルートなので、区切りの「/」と「d/」をつなぐと「/d/」になります。したがって、eは解答群のオです。
fの行:pathが「d/」、baseが「/a/b/c」の場合
pathの先頭に「../」や「./」はありません。そのためbaseを上の階層へ戻す処理はありません。
baseの末尾に「/」はないので、「/a/b/c」全体を残します。そこにプログラムが区切りの「/」を加え、pathの「d/」を続けます。
「/a/b/c」+「/」+「d/」=「/a/b/c/d/」
したがって、fは解答群のイです。
gの行:pathが「d」、baseが「/a/b/c/」の場合
pathの先頭に「../」や「./」はないため、階層を上がる処理はありません。
baseの末尾に「/」があるので、bpを一つ戻し、その末尾の「/」をbaseの残す部分から外します。残る部分は「/a/b/c」です。そこに区切りの「/」とpathの「d」を続けます。
「/a/b/c」+「/」+「d」=「/a/b/c/d」
path自体に末尾の「/」がないため、結果にも末尾の「/」は付きません。したがって、gは解答群のアです。
誤りやすいポイント
- 「../」は一つ上の階層へ移動しますが、ルートディレクトリより上には進みません。eでは4個目の「../」を処理しても位置は「/」のままです。
- baseの末尾に「/」がある場合は、その「/」を残す部分から外してから、区切りの「/」を加えます。末尾の「/」をそのまま残すと、区切りが重なってしまうように見えるため注意してください。
- 出力の末尾に「/」が付くかどうかは、pathの末尾に「/」があるかどうかで変わります。fのpathは「d/」ですが、gのpathは「d」です。
FAQ
Q: eでは「../../../../」の最後の「../」でエラーにならないのですか?
A: このプログラムは、bpがbaseの先頭より前に進まないようにしています。そのため、ルートより上へ進もうとしても位置は「/」のままです。
A: このプログラムは、bpがbaseの先頭より前に進まないようにしています。そのため、ルートより上へ進もうとしても位置は「/」のままです。
Q: fとgは、baseの指定が違うのに同じように扱えるのですか?
A: fのbaseには末尾の「/」がなく、gのbaseにはあります。gでは末尾の「/」を残す部分から外してから、プログラムが区切りの「/」を加えます。どちらも同じ区切り方でpathをつなげられます。
A: fのbaseには末尾の「/」がなく、gのbaseにはあります。gでは末尾の「/」を残す部分から外してから、プログラムが区切りの「/」を加えます。どちらも同じ区切り方でpathをつなげられます。
Q: ディレクトリやファイルが実際に存在するかも確認しますか?
A: いいえ。問題文では、ファイルシステム上に指定されたディレクトリやファイルが存在するかどうかのチェックは行わないとされています。この関数はパス文字列の変換を行います。
A: いいえ。問題文では、ファイルシステム上に指定されたディレクトリやファイルが存在するかどうかのチェックは行わないとされています。この関数はパス文字列の変換を行います。
関連キーワード: 絶対パス、相対パス、カレントディレクトリ、ディレクトリ、ポインタ



