初級C言語Q&A(13)

初出: C MAGAZINE 1996年6月号
Updated: 1996-07-01

[1つ前] [1つ後] [質問一覧] [記事一覧] [ホームページ]


標準ライブラリ


 C言語は関数という単位で処理を行うことにより、プログラマーがプログラムに 自由に新しい機能を追加することが簡単になっています。しかし、作った関数が 実は標準ライブラリにある関数だった、というのも無駄な話です。今回は標準ラ イブラリに関する大雑把な内容に関するQ&Aです。各関数の仕様の詳細はコン パイラに付属しているリファレンスマニュアルなどを参考にしてください。

ライブラリ


Q 【標準ライブラリ】

 標準ライブラリとは何か。

 標準的なC言語、一般的にはANSI Cで定められているライブラリ関数のことです。 あるいは、これらの関数が格納されたライブラリを意味することもあります。

 他の言語では、入出力のような処理は組み込まれた機能として用意されている 場合がありますが、C言語はこれらの機能も関数として用意されています。これ らの関数はプログラマーが自分で作成する関数と同じ位置付けのものであり、何 等特殊なものではありません。


Q 【ライブラリ】

 ライブラリとは何か。

 関数、モジュールをアーカイブとして一つのファイルの中に格納したものです。

 コンパイラは、コードをコンパイルして、最後に外部関数の呼び出しなどを解 決するために、指定されたオブジェクトファイルを検索します。ここで目的の関 数が見つからないと、ライブラリの中を検索して、目的の関数があればそれをプ ログラムの中に組み込む処理を行います。

 コンパイル時に指定したオブジェクトファイルは、プログラムの中に全て取り 込まれますが、ライブラリの中で目的の関数を発見した場合には、必要な関数だ けが組み込まれ、同じライブラリにある他の関数は組み込まれません。

 自作の関数をライブラリにしておけば、頻繁に使う関数をいちいち定義したり コンパイルしなくて済むようになります。


Q 【標準ライブラリ】

 標準ライブラリには、具体的には何が定義されているのか。

 表に示す関数やマクロが定義されています。これらの関数を使う場合には、表 に示したインクルードファイルを指定する必要があります。例えば<stdio.h> なら、#include <stdio.h>を目的の関数を使う前に書いておく必要があり ます。

<errno.h>(エラー) EDOM ERANGE errno <stddef.h>(共通の定義) NULL offsetof(型,メンバ指示子) ptrdiff_t size_t wchar_t <assert.h>(診断機能) NDEBUG void assert(int expression); <ctype.h>(文字操作) int isalnum(int c); int isalpha(int c); int iscntrl(int c); int isdigit(int c); int isgraph(int c); int islower(int c); int isprint(int c); int ispunct(int c); int isspace(int c); int isupper(int c); int isxdigit(int c); int tolower(int c); int toupper(int c); <locale.h>(文化圏固有操作) LC_ALL LC_COLLATE LC_CTYPE LC_MONETARY LC_NUMERIC LC_TIME NULL struct lconv char *setlocale(int category, cons char *locale); struct lconv *localeconv(void); <math.h>(数学) HUGE_VAL double acos(double x); double asin(double x); double atan(double x); double atan2(double y, double x); double cos(double x); double sin(double x); double tan(double x); double cosh(double x); double sinh(double x); double tanh(double x); double exp(double x); double frexp(double value, int *exp); double ldexp(double x, int exp); double log(double x); double log10(double x); double modf(double value, double *iptr); double pow(double x, double y); double sqrt(double x); double ceil(double x); double fabs(double x); double floor(double x); double fmod(double x); <setjmp.h>(非局所分岐) jump_buf int setjmp(jmp_buf env); void longjmp(jmp_buf env, in val); <signal.h>(シグナル操作) sig_atomic_t SIG_DFL SIG_ERR SIG_IGN SIGABRT SIGFPE SIGILL SIGINT SIGSEGV SIGTERM void (*signal(int sig, void (*func)(int))) (int); int raise(int sig); <stdarg.h>(可変個数実引数) va_list void va_start(va_list ap,最終引数 ); va_arg(va_list ap, 型); void va_end(va_list ap); <stdio.h>(入出力) _IOFBF _IOLBF _IONBF BUFSIZ EOF FILE FILENAME_MAX FOPEN_MAX fpos_t L_tmpnam NULL SEEK_CUR SEEK_END SEEK_SET size_t stderr stdin stdout TMP_MAX int remove(const char *filename); int rename(const char *old, const char *new); FILE *tmpfile(void) char *tmpnam(char *s); int fclose(FILE *stream); int fflush(FILE *stream); FILE *fopen(const char *filename, const char *mode); FILE *freopen(const char *filename, const char *mode, FILE *stream); void setbuf(FILE *stream, char *buf); int setvbuf(FILE *stream, char *buf, int mode, size_t size); int fprintf(FILE *stream, const char *format, ...); int fscanf(FILE *stream, const char *format, ...); int printf(const char *format, ...); int scanf(const char *format, ...); int sprintf(char *s, const char *format, ...); int sscanf(char *s, const char *format, ...); int vfprintf(FILE *stream, const char *format, va_list arg); int vprintf(const char *format, va_lis arg); int vsprintf(char *s, const char *format, va_list arg); int fgetc(FILE *stream); char *fgets(char *s, int n, FILE *stream); int fputc(int c, FILE *stream); int fputs(const char *s, FILE *stream); int getc(FILE *stream); int getchar(void); char *gets(char *s); int putc(int c, FILE *stream); int putchar(int c); int puts(const char *s); int ungetc(int c, FILE *stream); size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream); size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream); int fgetpos(FILE *stream, fpos_t *pos); int fseek(FILE *stream, long int offfset, int whence); int fsetpos(FILE *stream, const fpos_t *pos); long int ftell(FILE *stream); void rewind(FILE *stream); void clearerr(FILE *stream); int feof(FILE *stream); int ferror(FILE *stream); void perror(const char *s); <stdlib.h>(一般ユーティリティ) EXIT_FAILURE EXIT_SUCCESS MB_CUR_MAX NULL RAND_MAX div_t ldiv_t size_t wchar_t double atof(const char *nptr); int atoi(const char *nptr); long int atol(const char *nptr); double strtod(const char *nptr, char **endptr); long int strtol(const char *nptr, char **endptr, int base); unsigned long int strtoul(const char *nptr, char **endptr, int base); int rand(void); void srand(unsigned int seed); void *calloc(size_t nmemb, size_t size); void free(void *ptr); void *malloc(size_t size); void *realloc(void *ptr, size_t size); void abort(void); void atexit(void (*func)(void)); void exit(int status); char *getenv(const char *name); int system(const char *string); void *bsearch(const void *key, const void *base, size_t nmemb, size_t size, int (*compar)(const void *, const void *)); void qsort(void *base, size_t nmemb, size_t size, int (*compar)(const void *, const void *)); int abs(int j); div_t div(int numer, int denom); long int labs(long int j); ldiv_t ldiv(long int numer, long int denom); int mblen(const char *s, size_t n); int mbtowc(wchar_t *pwc, const char *s, size_t n); int wctomb(char *s, wchar_t wchar); size_t mbstowcs(wchar_t *pwcs, const char *s, size_t n); size_t wcstombs(char *s, const wchar_t *pwcs, size_t n); <string.h>(文字列操作) NULL size_t void *memcpy(void *s1, const void *s2, size_t n); void *memmove(void *s1, const void *s2, size_t n); char *strcpy(char *s1, const char *s2); char *strncpy(char *s1, const char *s2, size_t n); char *strcat(char *s1, const char *s2); char *strncat(char *s1, const char *s2, size_t n); int memcmp(const void *s1, const void *s2, size_t n); int strcmp(const char *s1, const char *s2); int strcoll(const char *s1, const char *s2); int strncmp(const char *s1, const char *s2, size_t n); size_t strxfrm(char *s1, const char *s2, size_t n); void *memchr(const void *s, int c, size_t n); char *strchr(const char *s, int c); size_t strcspn(const char *s1, const char *s2); char *strpbrk(const char *s1, const char *s2); char *strrchr(const char *s, int c); size_t strspn(const char *s1, const char *s2); char *strstr(const char *s1, const char *s2); char *strtok(const char *s1, const char *s2); void *memset(void *s, int c, size_t n); char *strerror(int errnum); size_t strlen(const char *s); <time.h>(日付及び時間) CLOCKS_PER_SEC NULL clock_t time_t size_t struct tm clock_t clock(void); double difftime(time_t time1, time_t time0); time_t mktime(struct tm *timeptr); time_t time(time_t timer); char *asctime(const struct tm *timeptr); char *ctime(const time_t timer); struct tm *gmtime(const time_t *timer); struct tm *localtime(const time_t *timer); size_t strftime(char *s, size_t maxsize, const char *format, const struct tm *timepttr);

Q 【関数が未定義となる】

 この関数は標準ライブラリの中にあるはずなのに、リンクすると未定義のエラ ーが出てしまう。

 コンパイラは正しくインストールされていますか。ライブラリを格納するディ レクトリはコンパイラが指定するものと一致している必要があります。処理系に よっては環境変数を設定して指定する場合もあります。定義ファイルに記述する 場合もあります。

 コンパイラが正しくインストールされているにも関らず未定義になる場合があ ります。標準関数の多くは、特に指定しなくても自動的に検索対象になるのです が、ある種の関数、特に数学関数は、明示的にライブラリを指定しなければ検索 対象にならないことがあります。-lm、-lmath、-lmathlibのようなオプションを 指定してみてください。オプションの指定方法は処理系に依存するので、確実に 指定するためにはマニュアルを参照してください。

 リンク時には、指定した順番にライブラリが検索されます。従って、指定する 順序は重要です。もしかすると、オプションの順序を入れ替えるとうまく行くか もしれません。


よく質問される関数


Q 【strcmp】

 次のコードは思った通りに動いてくれない。
void str_check(char *s1, char *s2)
{
    if (s1 == s2) {
        printf("文字列が一致しました¥n");
}

 文字列の比較はstrcmp という関数を使います。最初のコードは、文字列の中身ではなく、その文字列が メモリ上の同じアドレスに配置されているかどうかをチェックするため、文字列 の中身が同じであっても一致しない場合があります。
    if (strcmp(s1, s2) == 0)
        printf("文字列が一致しました¥n");

Q 【toupper】

 次のコードは、なぜ事前にislowerを呼び出さなければならないのか。
    if (islower(c))
        c = toupper(c);

 ANSIの仕様では、toupperislower で処理できる範囲の値を正しく処理できることになっています。従って、 islowerは無駄なコードです。しかし、昔のコンパイラの中には、 引数が小文字でなければ、toupperが正しく動作しないように設計 されているものがありました。このような処理系で動かすためのコードの名残な のでしょう。

(参考: comp.lang.c FAQ 13.5)


Q 【qsort】

 str_arrayは文字列へのポインタが100個格納された配列である。 次のコードはなぜうまく動作しないのか。
    char *str[];

    qsort(str_array, 100, sizeof(char *), strcmp);

qsortは、指定する比較関数に対して、比較されるオブジェクト を指すポインタを使った呼び出しを行います。言い換えると、呼び出された比較 関数は、与えられた引数abに対して、 *a*bを比較する仕様になっている必要があります。

 strcmpはこの仕様を満たしていないので、そのまま qsortの引数として用いることができません。 次のような関数を定義して、その中から strcmpを呼び出せば解決します。

int pstrcmp(const void *p1, const void *p2)
{
    return strcmp(*(char *const *) p1, *(char *const *) p2);
}

    ...
    qsort(str_array, 100, sizeof(char *), pstrcmp);
    ...
(参考: comp.lang.c FAQ 13.8)

Q 【qsortの警告】

 qsortを呼び出している行で、ポインタの型に疑惑があるという 警告メッセージが表示される。どうすればこの警告を出さないようにできるか。

 型が合致していないために警告が出るわけですから、型を合致させればよいの です。すなわち、次のような関数を用意すれば警告は出なくなります。型変換が 必要なら、この関数の中で実行します。
int compare(const void *p1, const void *p2)
{
    /*...*/
}
 もちろん、コンパイラの警告オプションを変更すれば、警告が出ないようにな るかもしれませんが…。警告を無視すると、いつか忘れた頃にひどい目に会うこ とが結構あるものです。

(参考: comp.lang.c FAQ 13.9)


Q 【時刻を得る】

 現在時刻を知る関数はあるか?

関数time()が用意されています。ただし、関数
time()
が戻す値はtime_tという型であり、そのままではおそらく使えない でしょう。そこで、この値をさらに別の関数を用いて使える形式に変換すること が必要になります。例えば、関数ctime()を使えば文字列にするこ とができます。

 関数gmtime()localtime()を使えば、 time_tの値をstruct tmという構造体に変換すること ができます。この構造体は、秒、分、時等の情報を数値として格納しており、プ ログラムの中で処理する場合に便利です。

(参考: comp.lang.c FAQ 13.12)


Q 【乱数】

 0からn-1までの範囲の整数を戻す乱数が欲しい。

 rand() % Nは、規則的な戻り値を生成することがあるため、 あまりよくないとされています。次のいずれかを使ってみてください。いずれも、 <stdlib.h>#includeすることを忘れないで ください。
    (int)((double)rand() / ((double)RAND_MAX + 1) * n)

    rand() / (RAND_MAX / n + 1)
(参考: comp.lang.c FAQ 13.16)

Q 【乱数】

 プログラムを実行すると、rand()を呼び出した結果、 毎回同じ数が出てくるようだが。

 プログラムの最初で、次のようなコードを実行すれば、1秒以内に実行しない 限り、毎回違った数が出てくるはずです。
    srand((unsigned int)(clock() / CLOCKS_PER_SEC));

Q 【歴史的な関数】

indexという関数は何をするのか?

 ANSIがC言語の仕様を規格にする以前によく使われていた関数です。この種のい くつかの関数は、ANSIに同様の処理をする別関数が定義されたため、現在はあま り使われなくなりました。

 次の各行の最初に示した関数は、後に示したようにANSI定義の関数を使って置き換えることができます。

    index(a, b)        strchr(a, b)
    rindex(a, b)       strrchr(a, b)
    bcopy(a, b, c)     memmove(b, a, c)
    bcmp(a, b, c)      memcmp(a, b, c)
    bzero(a, b)        memset(a, 0, b)
(参考: comp.lang.c FAQ 13.24)

Q 【ワイルドカード】

 作ろうとしているプログラムがワイルドカードを扱えるようにしたい。例えば、 コマンドfooに対して、foo *.cを実行させたい。 どうすればよいか。

 標準ライブラリには、ワイルドカードを処理するための機能は用意されていま せん。各処理系に独自の機能が追加されていることがあります。コンパイラのユ ーザーズマニュアルを読んでください。

(参考: comp.lang.c FAQ 13.7)


Q 【正規表現】

 ある文字列が指定した正規表現にマッチするかどうかを検査したい。

 正規表現を検査する関数は標準ライブラリにはありません。自作すると手間が かかりますが、正規表現ライブラリは、フリーのものがいくつも公開されている ので、これらを入手してみるのが妥当でしょう。

(参考: comp.lang.c FAQ 13.7)


ネットの質問

このコーナーは、WWWで出た質問や、ネットで見掛けた質問を紹介します。
(雑誌掲載時には、WWW初出の質問も含まれていますが、 オンラインQ&Aコーナーと重複しますので、 ここではカットしています。)

Q 【整数の切り捨て】

 ある整数の10の桁以下を切り捨てたい。どうすればよいか。

 C言語では、正の整数の割り算は、余りを切り捨てることが仕様で決まってい ます。従って、次のコードで目的の処理を実現することができます。
    a = (a / 100) * 100;
100で割った時点で余りは捨てられてしまうので、さらに100倍しても余りは戻ってきません。結果は100の倍数になります。

 実際に使う場合は、次のコードの方が簡単です。一度の除算で済んでいるので、効率も良いはずです。

   a -= a % 100;

Q 【整数の切り上げ(1)】

 ある整数の10の桁以下を切り上げたい。どうすればよいか。

 まず、その数を100で割ります。もし余りがあれば、(100 - 100で割った余り) を加えれば、10の桁以下は0となって、100以上の桁を1増やしたことになるはずで す。次のようなコードになります。
    if (a % 100 > 0)
        a += 100 - (a % 100);

Q 【整数の切り上げ(2)】

 10の桁を切り上げるのなら、次のコードでよいのでは?
    a = ((a + 99) / 100) * 100;

99を加えると、1の桁が0の時だけ繰り上がりが発生せず、1〜99の時には繰り上 がりが発生します。従って、その後に切り捨てを行ったら、最初にあった数を切 り上げたのと同じ結果になります。

 ですから、確かに、このコードで殆どの場合問題ありません。

 ただし、このコードは最初に足し算をしてから削ることになるので、きわどい 値に対してはオーバーフローが発生する確率が高くなります。具体的には、 a の最初の値が、 INT_MAX - 99 < a ≦ INT_MAX - (INT_MAX % 100) の時には、 ((a + 99) / 100) * 100 はオーバーフローが発生します。結果は正しくない値になりそうです。これを承 知の上で使うのであれば問題ありません。

 オーバーフローを検査したいのであれば、例えば次のようにします。

    mod = a % 100;
    if (mod > 0) {
        if (INT_MAX - a < 100 - mod) {
            /* オーバーフローの処理 */
        } else {
            a += 100 - mod;
        }
    }
 どちらの方法でも、INT_MAXにかなり近い数だとオーバーフロー が発生するし、負の数を入れると予期しないような結果になることを留意して使 う必要があります。

(参考 NIFTY-Serve C言語フォーラム(GO FC)


  ※ c.l.c FAQ : comp.lang.c FAQ list
     URL: http://www.eskimo.com/‾scs/C-faq.top.html
     文中の項目番号は新しい版に対応しています。旧版とは異なります。

(C) 1996 Phinloda, All rights reserved
無断でこのページへのリンクを貼ることを承諾します。問い合わせは不要です。
内容は予告なく変更することがあります。