C言語基礎|文字列関連の標準ライブラリ

文字列処理は車の運転と同じ!―安全装備(string.h)を使えば、速く・正確に・事故(バグ)を減らせる。

これまで、strlen や文字列コピーを自作して「仕組み」をしっかり理解してきました。ここからは一歩進んで、実務で本当に使う武器=標準ライブラリを使いこなしていきます。

C言語の文字列は、\0(ナル文字)で終わる char の並びです。だからこそ、コピー・連結・比較・検索などの処理は定番化されていて、<string.h> に便利な関数がまとまっています。
自作するより 速い読みやすいテスト済み のことが多いので、まずは標準関数を正しく使えることが大事です。

<string.h> の代表的な関数一覧(用途別に整理)

提示された Table 11C-1 を、学習しやすいように「目的」でまとめ直します。

分類関数何をする?(超要約)
長さstrlen文字列の長さ(\0を除く)
コピーstrcpy / strncpy文字列をコピー(n制限あり/なし)
連結strcat / strncat末尾に追加(n制限あり/なし)
比較strcmp / strncmp辞書順比較(n制限あり/なし)
ロケールstrcoll / strxfrmロケール依存の比較用
検索strchr / strrchr文字を探す(先頭/末尾)
検索strstr部分文字列を探す
構成strspn / strcspn許可文字だけの連続長 / 禁止文字までの長さ
分割strtok区切りで分解(状態を持つので注意)
メモリmemset連続領域を同じ値で埋める
メモリmemcpy / memmove連続領域コピー(重なり対応の違い)
メモリmemchr / memcmp探索 / 比較(バイト列として)

restrict って何?(超ざっくり安全メモ)

プロトタイプに restrict が付くことがあります。これはコンパイラに対して、

  • このポインタが指す領域は、他の restrict ポインタが指す領域と重ならない前提で最適化していいよ

という意味合いです。
そのため、例えば strcpy は コピー元とコピー先が重なる状況は未定義です。重なる可能性があるなら memmove を使う、という判断につながります。

strcpy 関数:文字列をコピーする

書式

  • ヘッダ:#include <string.h>
  • 形式:char *strcpy(char * restrict s1, const char * restrict s2);

何をする命令なのか(役割)

  • s2 が指す文字列(\0 まで)を、s1 が指す配列へコピーする
  • コピー元とコピー先が重なると動作は未定義
  • 返り値は s1(コピー先の先頭)を返す

strcpy の要点

項目内容
コピー範囲\0 まで全部コピー(\0 も含む)
コピー先の条件十分な大きさの配列(バッファ)が必要
重なり未定義(避ける)
返り値コピー先の先頭ポインタ(s1)

strncpy 関数:文字数を制限してコピーする

書式

  • ヘッダ:#include <string.h>
  • 形式:char *strncpy(char * restrict s1, const char * restrict s2, size_t n);

何をする命令なのか(役割)

  • 最大 n 文字ぶんコピーする
  • s2 の長さが n 未満なら、残りを \0 で埋める
  • s2 の長さが n 以上なら、\0 を付けない(ここが事故ポイント!)
  • 返り値は s1

strncpy の事故ポイント

状況どうなる?結果
s2 の長さ < n残りを \0 埋め文字列として成立しやすい
s2 の長さ >= n\0 をコピーしない文字列にならない可能性あり(危険)

strcpy と strncpy の違い(イメージ)

strcpy:    コピー元が終わるまで(\0 まで)全部コピー

strncpy:   n 文字だけコピー(\0 は入らないことがある)
           → 表示や strlen が暴走する原因になりうる

サンプルプログラム

「入力した文字列を safety という配列にコピーし、さらに先頭3文字だけを short にコピーする」プログラム例です。

プロジェクト名:chap11-9-1 ソースファイル名:chap11-9-1.c

Visual Studio でこのプログラムを実行するには、SDLチェック設定を変更しておく必要があります。
1.プロジェクト名を右クリックして、「プロパティ」をクリックします。
2.「C/C++」→「全般」→「SDLチェック」を「いいえ」に切り替えて「OK」をクリックします。

#include <stdio.h>
#include <string.h>

int main(void)
{
    char input[128];
    char safe[128];
    char short3[4];   // 3文字 + \0

    printf("好きな単語を入力してください:");
    scanf("%127s", input);

    strcpy(safe, input);
    printf("丸ごとコピーしました:%s\n", safe);

    strncpy(short3, input, 3);
    short3[3] = '\0';   // strncpy の落とし穴対策:自分で終端を付ける
    printf("先頭3文字だけコピーしました:%s\n", short3);

    return 0;
}

ポイント:

  • scanf は %127s にして安全寄りにしています。
  • strncpy を使ったあと 必ず short3[3] = '\0' を入れて「文字列として成立」を保証しています。
    ここがめちゃ大事です。

strcpy の返り値が便利な理由(連続処理が書ける)

strcpy はコピー先の先頭を返すので、次のように「コピーしながら表示」ができます。

例:コピーしてすぐ表示

printf("結果:%s\n", strcpy(safe, input));

なぜこう書ける?

関数返すものだからできること
strcpys1(コピー先の先頭)そのまま printf の %s に渡せる
strncpys1(コピー先の先頭)同様に渡せるが \0 には注意

strncpy がバグにつながる典型例(なぜ危ない?)

例えば次のようにすると、s は \0 で終わらない可能性があります。

char s[5] = {'X','X','X','X','X'};
strncpy(s, "12345", 2);

結果は先頭2文字だけ書き換わり、残りは X のままです。
つまり、s のどこにも \0 が無ければ 文字列として扱えず、printf の %s や strlen がメモリの奥まで探しに行って危険です。

演習問題

演習11-4

次の仕様の関数 my_strcpy と my_strncpy を作成してください。

  • my_strcpy(d, s) は s を d に \0 までコピーし、d を返す
  • my_strncpy(d, s, n) は最大 n 文字コピーし、d を返す
    ただし標準 strncpy と同じ仕様(n 以上なら \0 を付けない)にする

プロトタイプ:

#include <stddef.h>
char *my_strcpy(char *d, const char *s);
char *my_strncpy(char *d, const char *s, size_t n);

解答例

#include <stddef.h>

char *my_strcpy(char *d, const char *s)
{
    char *t = d;
    while ((*d++ = *s++) != '\0')
        ;
    return t;
}

char *my_strncpy(char *d, const char *s, size_t n)
{
    char *t = d;

    while (n > 0 && *s != '\0') {
        *d++ = *s++;
        n--;
    }
    while (n > 0) {     // 余りは \0 埋め
        *d++ = '\0';
        n--;
    }

    return t;
}

解説

  • my_strcpy は \0 までコピーするので、コピー後は必ず文字列になります。
  • my_strncpy は「余ったら \0 埋め」だが、「足りない(nが小さい)と \0 を入れない」ケースがあり得ます。
    → 使う側が終端を保証する設計が必要です。