13章 ライブラリ関数



13.1:
どうすれば数を文字列に変換することができるか(atoiの反対)。関数 itoaというのは存在するのか。

A:

なにも考えずにsprintf()を使え。(「sprintfはやりすぎだ。実行時 間とコード領域を無駄遣いする。」という声は無視すること。とにか く動くんだから。) 質問7.5の解答内の例を参照のこと。質問12.21も 参照のこと。

すぐわかるようにsprintf()を使えばlongや浮動小数点数も文字列に 変換することができる。

References:
K&R1 Sec. 3.6 p. 60; K&R2 Sec. 3.6 p. 64.


13.2:
なぜstrncpy()はコピー先の文字列に、終端文字の'\0'を付けないこ とがあるのか。

A:

元々はstrncpy()は、今となっては時代遅れになってしまったデータ 構造、すなわち固定長で必ずしも\0で終わるとは限らない"文字列"を 扱うために設計された(strncpy()の関連する奇妙なところは、指定さ れた長さになるまで\0を埋めることである)。確かにこの"文字列"以 外を扱うときは少し間抜けである。しばしば目的の文字列に、自分で' \0'を追加しなければならない。この問題はstrncpy()のかわりに strncat()を使うことで回避することができる。もしコピー先の文字 列が最初は空なら、strncat()が、strncpy()がやってくれると思って いたことをやってくれる。他にはsprintf(dest, "%.*s", n, source) が考えられる。

(文字列ではなく)任意のバイトをコピーするときには、普通は memcpy()のほうがstrncpy()よりも適切なルーチンである。


13.5:
どうしてtoupper()の中には大文字を与えると変な動きをするものが あるのか。どうしてコードによっては、toupper()を呼ぶ前に islower()を呼んでいるのか。

A:

古い版のtoupper()やtolower()は変換が必要でない引数(数字や句読 点や既に文字が望みのcaseに(toupper()の場合は大文字に、 tolower()の場合は小文字に)なっているときは)には正しく動かない 場合があった。ANSI/ISO 規格のCでは、これらの関数はすべての文字 引数に対してきちんと動くことが保証されている。

References:
ANSI Sec. 4.3.2; ISO Sec. 7.3.2; H&S Sec. 12.9 pp. 320-1; PCS p. 182.


13.6:
空白によって区切られた引数からなるコマンド行がある。これを main()の引数として与えられるargcとargvのように分けるにはどうす ればよいか。

A:

この種の"字句単位に切る(tokenizing)"ルーチンで標準で使えるのは strtok()だけである。ただし使うにはコツがいるし、やりたいことを 全部やってくれるとは限らない(例えば引用符の問題)。

References:
K&R2 Sec. B3 p. 250; ANSI Sec. 4.11.5.8; ISO Sec. 7.11.5.8; H&S Sec. 13.7 pp. 333-4; PCS p. 178.


13.7:
正規表現とかワイルドカードを使った比較をするコードが必要となっ た。

A:

まず、古典的な正規表現(Unixのedやgrepといったユーティリティーで 使われている)とファイル名のワイルドカード(たいていのオペレーティ ングシステムで使われている)の違いが分かっているかどうか確認す ること。

正規表現による比較をするパッケージは数多く用意されている。たい ていのパッケージは一組の関数からなっている。正規表現を"コンパ イル"する関数と、その結果を"実行"する関数である(結果と文字列の 比較をする)。<regex.h>とか<regexp.h>といった名前のヘッダーファ イルや、regcmp()/regex()とかregcomp()/regexec()とか re_comp()/re_exec()といった名前の関数の組を探すこと(これらの組 は別れて別々のregexpライブラリーに存在するかもしれない)。

自由に配布していい人気のあるregexpパッケージとしてHenry Spencer の書いたものがある。これはcs.toronto.eduの pub/regexp.shar.Zや、その他のアーカイブから入手可能である。 GNUプロジェクトはrxという名前のパッケージを用意している。質問 18.16も参照のこと。

ファイル名のワイルドカードによる一致(globbingとも呼ばれる)はシ ステムによってやりかたが異なる。Unixでは、ワイルドカードはプロ セスが起動される前にシェルによって自動的に展開されるので、プロ グラムが心配しなければならない場合は滅多にない。MS-DOSのコンパ イラーの多くは、特別なオブジェクトファイルを用意していて、プロ グラムとリンクするとargvを作るときにワイルドカードを展開してく れる。いくつかのシステム(MS-DOSやVMSを含む)はワイルドカードで 指定されたファイルの一覧を表示したりファイルを開いたりするサー ビスを用意している。コンパイラ/リンカーについてきた資料をよく 読むこと。


13.8:
文字列の配列をqsort()で整列するのに、strcmp()を比較用の関数と して使用しているが、うまくいかない。

A:

"文字列の配列"とはたぶん"charへのポインターからなる配列"を意味 していると思う。qsortの比較用の関数の引数は、整列の対象へのポ インターである。ここではcharへのポインターへのポインターである (strcmpはもちろんcharへのポインターを引数とする)。比較用の関数 の引数は"汎用のポインター"、すなわちconst void * あるいはchar *であらわされる。これを実際の型(char **)に戻して間接参照すると char *を得る。これは比較に使える。以下のような比較の関数を用意 すること。

/* ポインターを通して比較する */
int pstrcmp(const void *p1, const void *p2)
{
return strcmp(*(char * const *)p1, *(char * const *)p2);
}

比較に使う関数の引数は"汎用のポインター"const void *で表わされ る。引数は実際の型(char **)に再び変換されて、間接参照される。 その結果char *の引数となってstrcmp()に渡される。(ANSI以前のコ ンパイラで使うときは、ポインター引数をvoid *ではなくchar *と宣 言して、constを削る。)

(K&R 2 Sec. 5.11 pp. 119-20の議論を読むときは用心すること。そ こでは規格のライブラリーのqsortを議論しているわけではない。)

References:
ANSI Sec. 4.10.5.2; ISO Sec. 7.10.5.2; H&S Sec. 20.5 p. 419.


13.9:
qsort()を使って構造体の配列を整列しようとしている。私が書いた 比較ルーチンは、構造体へのポインターを引数として取る。けれどコ ンパイラは、私の関数がqsort()の引数としては間違ったデータ型だ と文句を付ける。関数へのポインターをどのようにキャストすれば警 告を消し去ることができるのか。

A:

型の変換を比較関数内で行わなければならない。比較関数は上の Q12.2で議論したように"汎用のポインター" (const void *)を引数と して取ると宣言されてなければならない。コードは以下のようになる だろう。

int mystructcmp(const void *p1, const void *p2)
{
const struct mystruct *sp1 = p1;
const struct mystruct *sp2 = p2;
/* これからsp1->whateverとsp2-> ...を比較する */

(汎用のポインターを構造体mystructを指すポインターに変換するの は、初期化sp1 = p1 と sp2 = p2で行われる。p1とp2がvoidのポイン ターであるから、コンパイラーが暗黙のうちに変換を行う。ANSI以前 のコンパイラーを使うときは明示的なキャストとchar *のポインター を使うことが必要となる。質問7.7も参照のこと。)

一方、構造体へのポインターを整列するのであれば質問13.8のように 間接参照が必要である。

sp1 = *(struct mystruct **)p1 )

一般に、"コンパイラーを黙らせる"だけのためにキャストをつけるの はいい考えではない。コンパイラーの警告は、たいてい何かを伝えよ うとしてるわけで、自分がなにをやってるかよく理解していないのに、 無視したり黙らせたりするとひどいめにあう。

References:
ANSI Sec. 4.10.5.2; ISO Sec. 7.10.5.2; H&S Sec. 20.5 p. 419.


13.10:
リンク付きリストをどうやってソートすればよいか。

A:

リストを作っている間ずっとリストの要素の順が乱れないようにして おくほうが(あるいは木(tree)を使うほうが)、後でソートするよりも やさしいときもある。挿入ソート(insertion sort)やマージソート (merge sort)のようなアルゴリズムはリンク付きリストといっしょに 使うのに向いている。標準のライブラリー関数を使いたいなら、ポイ ンターの配列を一時的に確保して、配列内の各ポインターをそれぞれ リストの各ノードを指すようにする。qsort()を呼んで、最後にソー ト済みの配列に基づいてリストのポインターを再構築する

References:
Knuth Sec. 5.2.1 pp. 80-102, Sec. 5.2.4 pp. 159-168; Sedgewick Sec. 8 pp. 98-100, Sec. 12 pp. 163-175.


13.11:
メモリに収まりきらない量のデータをどうやってソートすればよい か。

A:

「外部ソート(external sort)」を使えばよい。これは例のKnuthの第 3巻で読むことができる。基本的な概念はデータを塊にしてソートし て(一度にメモリに納まるほどの大きさで)、ソート済みの各塊を一 時的にファイルに書き込んで、ファイルを一つにまとめる。O/Sが汎 用の整列ユーティリティーを提供してるかもしれない、提供されてい たらプログラムの中から呼び出してみればいい。質問19.2719.30も 参照のこと。

References:
Knuth Sec. 5.4 pp. 247-378; Sedgewick Sec. 13 pp. 177-187.


13.12:
Cのプログラムで時刻を得るのはどうすればよいか。

A:

関数time()、ctime()、localtime()を使う(これらの関数は昔から存 在するしANSI規格の中にも存在する)。以下にサンプルプログラムを 載せておく。

#include <stdio.h>
#include <time.h>

main()
{

time_t now;
time(&now);
printfprintf("It's %.24s.\n", ctime(&now));
return 0;
}

References:
K&R2 Sec. B10 pp. 255-7; ANSI Sec. 4.12; ISO Sec. 7.12; H&S Sec. 18.


13.13:
ライブラリー関数localtime()はtime_tを構造体tmに変換することと、 ctime()はtime_tを印刷可能な文字列に変換するということは知って いる。さて構造体tmや文字列をtime_tに変換する逆変換はどうやって おこなえばよいか。

A:

ANSI Cは、構造体tmをtime_tに変換するライブラリー関数mktime()を 用意している。

文字列をtime_tに変換するのは、もう少し骨が折れる。なぜなら解析 しなければならない日付や時間の書式の種類が広範囲にわたるからで ある。strptime()という関数を用意しているシステムもある。これは 基本的にはstrftime()の逆の動作をする。他に人気のあるルーチンと してはpartime(RCSのパッケージとともに配布されている)や getdate() (その他のいくつかの関数と共にCニュースのパッケージで) がある。質問18.16を参照のこと。

References:
K&R2 Sec. B10 p. 256; ANSI Sec. 4.12.2.3; ISO Sec. 7.12.2.3; H&S Sec. 18.4 pp. 401-2.


13.14:
ある日からN日後が何月何日かをどうやって計算するのか。2つの日付 の差はどうやって計算するのか。

A:

ANSI/ISO規格のC言語は、mktime()とdifftime()という関数を、質問 の要求に答えるために用意している。mktime()は正規化されていない 日付を引数に取る。データを設定済みの構造体tmを用意して、 tm_fieldフィールドに日付を足したり引いたりしてからmktime()を呼 んで年・月・日のフィールドを正規化(ついでにtime_t型の値に変換) することは難しくない。difftime()は二つのtime_tの値の差を秒で計 算する。引き算に使う日付のtime_tを計算するのにmktime()を使うこ とができる。

これらの解はtime_tで表現できるに日付が収まるときにだけ正しく動 くことを保証されている。tm_mdayフィールドはintで、32,736より大 きな日付のオフセットはオーバーフローする可能性がある。夏時間と の切替時には、その土地の1日は24時間ではないことにも注意するこ と。

両方の質問の別の取り組みかたとしては"ユリウス日(Julian day)"数 を使う方法がある。ユリウス日ルーチンの実装はSimtel/Oaklandアー カイブ(質問18.16参照)からJULCAL10.ZIPという名前で入手可能であ る。参考文献に挙げた"Data conversions"という記事からも得ること ができる。

質問13.13, 20.31, 20.32も参照のこと。

References:
K&R2 Sec. B10 p. 256; ANSI Secs. 4.12.2.2,4.12.2.3; ISO Secs. 7.12.2.2,7.12.2.3; H&S Secs. 18.4,18.5 pp. 401-2; David Burki, "Date Conversions".


13.15:
乱数発生器が欲しい。

A:

標準のCライブラリにrand()というのが存在する。君が使っているシ ステムのrand()の実装は完璧でないかもしれないが、よりよい関数を 書くのは容易でないことも事実である。

自分で乱数発生器を実装するはめに陥ったとしても数多くの文献が出 まわっている。Referencesを参照のこと。ネットの上にもたくさんの パッケージが流れている。r250とかRANLIBとかFSULTRAといった名前 のパッケージを探すこと(質問18.16参照)。

References:
K&R2 Sec. 2.7 p. 46, Sec. 7.8.7 p. 168; ANSI Sec. 4.10.2.1; ISO Sec. 7.10.2.1; H&S Sec. 17.7 p. 393; PCS Sec. 11 p. 172; Knuth Vol. 2 Chap. 3 pp. 1-177; Park and Miller, "Random Number Generators: Good Ones are hard to Find".


13.16:
ある範囲の整数からなる乱数はどうやったら生成することができるか。

A:

すぐに思い付く、

rand() % N

(これは0からN-1までの数を返そうとする)は乱数の質が低い。なぜな ら乱数発生器の多くで下位のビットは悲惨なほどランダムでない(質 問13.18を参照のこと)。よりよい方法は以下のようなものである。

(int)((double)rand() / ((double)RAND_MAX + 1) * N)

浮動小数を使うことが気になるのなら、以下の方法を試せばよい。

rand() / (RAND_MAX / N + 1)

どちらの方法もRAND_MAX(ANSIはで定義している)の値を知っ ていることが当然必要である。またどちらもNがRAND_MAXにくらべて 十分小さいことを仮定している。

(ところで、RAND_MAXはCライブラリーのrand()関数が返す値の範囲を 表わす定数である。RAND_MAXを他の値に変更することはできないし、 rand()に別の範囲の数を返すように指定することもできない。)

0から1の間の小数を返す乱数発生器を元にするなら、単にその乱数発 生器の出力にNをかけると0からN-1の範囲の乱数発生器が得られる。

References:
K&R2 Sec. 7.8.7 p. 168; PCS Sec. 11 p. 172.


13.17:
私のプログラムを走らせるたび、いつも関数rand()から同じ乱数列が 返ってくる。

A:

srand()を使って、擬似乱数発生器に本当にランダムな初期値を与え ればよい。よくある乱数の種は、時刻やユーザーがキーを押してから の経過時間である(ただしキーが押された時間というのは移植性の高 い方法では求めにくい。質問19.37を参照のこと)。(プログラム中で srand()を2回以上呼んで役にたつことは滅多にない。"本当にランダ ムな"数を得ようと思って、rand()を呼ぶ前に毎回srand()を呼ぶよう な真似はぜったいにしてはいけない。)

References:
K&R2 Sec. 7.8.7 p. 168; ANSI Sec. 4.10.2.2; ISO Sec. 7.10.2.2; H&S Sec. 17.7 p. 393.


13.18:
真偽値からなる乱数が欲しいのでrand() % 2を使ったところ、結果は 0と1が交互に現れるだけだった。

A:

出来の悪い擬似乱数発生器では(いくつかのシステムに乗っているも のは不幸なことにそうである)下位のビットはあまりランダムではな い。上位のビットを使うこと。質問13.16を参照のこと。


13.20:
正規分布またはガウス分布の乱数を生成するのはどうすればよいか。

A:

以下のBoxとMullerの方法(極座標法)はKnuthご推薦である。

#include <stdlib.h>
#include <math.h>

double gaussrand()
{

static double V1, V2, S;
static int phase = 0;
double X;

if(phase == 0) {

do {
double U1 = (double)rand() / RAND_MAX;
double U2 = (double)rand() / RAND_MAX;

V1 = 2 * U1 - 1;
V2 = 2 * U2 - 1;
S = V1 * V1 + V2 * V2;

} while(S >= 1 || S == 0);

X = V1 * sqrt(-2 * log(S) / S);

} else
X = V2 * sqrt(-2 * log(S) / S);

phase = 1 - phase;

return X;
}
この他の方法については、このFAQの拡張版(質問20.40参照)を参照の こと。

References:
Knuth Sec. 3.4.1 p. 117; Box and Muller, "A Note on the Generation of Random Normal Deviates"; Press et al., _Numerical Recipes in C_ Sec. 7.2 pp. 288-290.


13.24:
古いプログラムを移植しようとしている。なぜ「未定義の外部シンボ ル」というエラーが出るのか。

A:

これから(左側に)挙げるルーチンはそれぞれ時代遅れである。代わり に右側のルーチンを使え。

index?strchrを使え。
rindex?strrchrを使え。
bcopy?一番目の引数と二番目の引数を入れ替えてmemmoveを使え(質問11.25も参照のこと)。
bcmp?memcmpを使え。
bzero?二番目の引数を0にしてmemsetを使え。
逆に、右側の列の関数が載っていない古いシステムを使っているのな ら、左側の関数を右側の関数を使って実装、あるいは代用することが できるかもしれない。

References:
PCS Sec. 11.


13.25:
「ライブラリのルーチンが未定義」というエラーが出たままである。 正しいヘッダーファイルをすべて#includeしたのに。

A:

いくつかの場合(特に関数が標準でない場合)、プログラムをリンクす るときに、正しいライブラリが見つかるように、明示的にパスを指定 しなければならない。質問11.30, 13.26, 14.3を参照のこと。


13.26:
それでも「ライブラリーのルーチンが未定義」というエラーが出たまま である。今度はリンクするときに-lを付けてライブラリーを指定したの に。

A:

多くのリンカーは、指定したオブジェクトファイルとライブラリーの リストを一回のパスで作り出し、ライブラリーからその時点で未定義 の関数を含むモジュールだけを取り出す。だからオブジェクトファイ ルとライブラリーのコマンドラインでの順序は重要である(オブジェ クトファイル間の順序も大事である)。普通はライブラリーを最後に 探してもらいたい(例えば、UNIXでは-lスイッチの類はすべてコマン ドラインの最後のほうに付けること)。質問13.28も参照のこと。


13.28:
リンカーが_endが未定義だと文句をつけてくるのはどういうときか。

A:

このメッセージは、古いUnixのリンカーの変なところである。_endが 未定義であるというエラーが出るのは、他にも未定義のものがあると きだけである。その他の部分を修正する。そうすれば_endのエラーは 消える。(質問13.25, 13.26も参照のこと。)

目次へ戻る