C言語のきほん|文字列操作関数を使う

文字列をもっと自由にあつかおう。文字列操作関数を知ると、C言語の表現力がぐんと広がる

C言語でプログラムを書いていると、文字列をあつかう場面はとても多くあります。
たとえば、名前を表示したり、メッセージを作ったり、入力された語句の長さを調べたり、別の配列へ文字列を写したりするときですね。

ただし、C言語には、ほかの多くのプログラミング言語のような「文字列型」がありません。
C言語では、文字列は文字の配列としてあつかわれ、最後に文字列の終わりを表すナル文字 \0 が付いていることで「ここまでが文字列です」と判断されます。

このしくみは最初は少し不便に感じるかもしれません。
でも、そのぶんC言語では、文字列の中身やメモリの動きを意識しながら、柔軟に文字列を操作できます。
そして、その操作を助けてくれるのが、標準ライブラリの文字列操作関数です。

たとえば、

  • 文字列の長さを調べる strlen
  • 文字列をコピーする strcpy
  • 指定した文字数だけコピーする strncpy

といった関数があります。

これらの関数を使えるようになると、自分で1文字ずつ数えたり、1文字ずつ写したりしなくても、文字列に関する処理をすっきり書けるようになります。
また、「ナル文字を含めて考える必要がある」「コピー先の配列サイズを意識する必要がある」など、C言語らしい大事な考え方もいっしょに身につきます。

この節では、文字列操作関数とは何かを確認したあと、strlen、strcpy、strncpy を中心に、役割や使い方、注意点をていねいに見ていきます。
C言語の文字列処理は、最初は少し独特に見えますが、基本がわかるととても面白くなってきます。ここでしっかり慣れていきましょう。

文字列操作関数とは

C言語では、文字列は特別な型ではなく、文字が連続して並んだ配列として表現されます。
そして、その最後には必ず文字列の終わりを表すナル文字 \0 が付きます。

たとえば、文字列 ABC は、内部的には次のようなイメージです。

要素内容
1文字目A
2文字目B
3文字目C
終端\0

このようなしくみなので、C言語では「文字列の長さを知りたい」「別の配列へ文字列をコピーしたい」といった処理をするとき、配列とナル文字を意識する必要があります。
そのために用意されているのが、文字列操作関数です。

文字列操作関数を使うときは、まず次のヘッダファイルをインクルードします。

#include <string.h>

この string.h の中に、文字列をあつかうためのいろいろな関数が宣言されています。

文字列操作関数を使うときの基本

文字列操作関数を使うときに大切な基本を、先に整理しておきましょう。

ポイント内容
ヘッダファイルが必要string.h をインクルードする
文字列は文字配列文字列型ではなく文字の配列としてあつかう
終端に \0 が必要ナル文字がないと文字列の終わりがわからない
配列サイズに注意コピー先が小さいと危険になる

この4つは、今後どの文字列操作関数を学ぶときにも共通して大切です。

なぜ文字列操作関数が必要なのか

C言語では、文字列をそのまま代入したり比較したりすることは、思ったようにはできません。
たとえば、配列どうしに対して、普通の変数のようにそのまま代入を書くことはできません。

たとえば、整数なら次のような代入ができます。

int a = 10;
int b = a;

でも、文字配列では次のようなことはできません。

char s1[10] = "ABC";
char s2[10];

s2 = s1;

このような書き方はできないため、文字列を別の配列へ写したいときは strcpy などの関数を使います。

また、文字列の長さも、整数のようにすぐわかるわけではありません。
文字列は \0 に出会うまで続いているので、長さを調べるには先頭から順に見ていく必要があります。その処理を行ってくれるのが strlen です。

つまり、文字列操作関数は、C言語で文字列を実用的にあつかうための基本の道具なのです。

文字列の長さを取得する strlen

strlen は、文字列の長さを調べる関数です。
ここでいう長さとは、ナル文字 \0 を除いた文字数のことです。

関数宣言は次の通りです。

#include <string.h>

size_t strlen(const char *s);

strlen の意味を整理する

項目内容
関数名strlen
引数長さを調べたい文字列
返却値文字列の長さ
注意点\0 は長さに含まれない

たとえば、次のようになります。

char s[] = "Hello";
size_t len = strlen(s);

このとき len には 5 が入ります。
文字列 Hello は内部的には H e l l o \0 ですが、長さとして数えるのは H から o までの5文字だけです。

const が付いている意味

strlen の引数は const char *s になっています。
この const は、「この関数の中では文字列の内容を書き換えません」という意味です。

つまり strlen は、文字列の中身を変えるのではなく、あくまで長さを調べるだけの関数です。
そのことが関数宣言にも表れています。

size_t とは

strlen の返却値は size_t 型です。
これは、配列の大きさやメモリサイズなどを表すためによく使われる型です。

そのため、printf で表示するときは %zu を使います。

printf("文字数は %zu です。\n", len);

ここは細かいようですが、とても大事です。strlen の返却値は int ではなく size_t なので、表示するときもそれに合った変換指定を使います。

strlen の動きを図でイメージしよう

strlen は、文字列の先頭から順に見ていき、\0 が出てくる手前までの文字数を数えます。
この流れを図でイメージすると、とてもわかりやすいです。

この図では、文字列 CDemo の末尾に \0 が付いている様子を表しています。
strlen は、先頭から順番に文字を確認し、\0 に到達する直前までの文字数を数えます。
そのため、\0 自体は長さに含まれません。

このイメージを持っておくと、「配列の大きさ」と「文字列の長さ」は同じではないことも見えてきます。
たとえば、配列が20要素あっても、文字列の長さは5文字しかないこともあります。

strlen を使ったシンプルなプログラム例

入力した言葉の文字数を調べて表示するプログラムです。

ファイル名:12_5_1.c

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

int main(void)
{
    char word[100];

    printf("好きな言葉を入力してください > ");

    /* 文字列を読み込む */
    if (fgets(word, sizeof(word), stdin) != NULL) {
        size_t len = strlen(word);

        /* 改行が含まれていれば長さから除いて見やすくする */
        if (len > 0 && word[len - 1] == '\n') {
            word[len - 1] = '\0';
            len--;
        }

        printf("「%s」の文字数は %zu です。\n", word, len);
    } else {
        puts("文字列を読み取れませんでした。");
    }

    return 0;
}

このプログラムのポイント

このプログラムでは、fgets で文字列を読み込んでいます。
fgets は改行文字 \n も読み込むことがあるため、そのまま strlen を使うと、Enter による改行まで長さに含まれてしまうことがあります。

そこで、

if (len > 0 && word[len - 1] == '\n')

で末尾が改行かどうかを確認し、改行なら \0 に書き換えています。
こうすると、見た目どおりの文字数を表示しやすくなります。

文字列をコピーする strcpy

次は、文字列を別の配列へコピーする strcpy を見ていきましょう。

関数宣言は次の通りです。

#include <string.h>

char *strcpy(char * restrict s1, const char * restrict s2);

strcpy の役割

strcpy は、s2 が指す文字列を s1 にコピーします。
このとき、最後の \0 も含めてコピーします。

項目内容
関数名strcpy
コピー元s2
コピー先s1
特徴\0 まで含めて丸ごとコピーする

たとえば、

char src[] = "ABC";
char dest[10];

strcpy(dest, src);

とすると、dest の中身は ABC になります。
内部的には A B C \0 がコピーされています。

なぜ配列サイズに注意が必要なのか

strcpy はとても便利ですが、コピー先の配列が十分に大きくないと危険です。
たとえば、コピー元が 10文字以上あるのに、コピー先が 5要素しかないと、配列の外にはみ出して書き込んでしまう可能性があります。

これをバッファオーバーフローといいます。

そのため strcpy を使うときは、必ず

  • コピー元の文字列長
  • コピー先の配列サイズ
  • \0 のぶんの1文字

を意識する必要があります。

strcpy の動きを図で見てみよう

この図では、コピー元の文字列が末尾の \0 まで含めてコピー先へ移される様子を表しています。
strcpy は文字だけを移すのではなく、文字列の終わりを示す \0 までコピーするところが大切です。
そのため、コピー先には、文字数ぴったりではなく、最後の \0 まで入る大きさが必要になります。

strncpy とは

strncpy は、strcpy と似ていますが、最大で何文字コピーするかを指定できる関数です。

関数宣言は次の通りです。

#include <string.h>

char *strncpy(char * restrict s1,
              const char * restrict s2, size_t n);

strncpy の役割

strncpy は、s2 の文字列を s1 に対して最大 n 文字までコピーします。

項目内容
関数名strncpy
コピー元s2
コピー先s1
特徴最大 n 文字までコピーする

これだけ見ると、とても安全そうに見えます。
でも、strncpy には注意点があります。

strncpy の注意点

いちばん大切なのは、コピー元が n 文字以上ある場合、\0 が自動で付かないことがあるという点です。

たとえば、

char dest[10] = "";
char src[] = "ABCDE";

strncpy(dest, src, 4);

とすると、dest には A B C D までがコピーされます。
でも、このときコピーした範囲のあとに必ず \0 が付くとは限りません。

そのため、strncpy を使うときは、

  • あらかじめ配列を \0 で初期化しておく
  • あるいは自分で最後に \0 を入れる

といった対策が必要になることがあります。

strncpy の動きを図で見てみよう

この図では、コピー元の A B C D E \0 のうち、最初の4文字だけがコピーされる様子を表しています。
ここで大切なのは、E や \0 まではコピーされないという点です。
そのため、コピー先を文字列として安全に使うには、もともと \0 で初期化しておくか、自分で末尾を整える必要があります。

strcpy と strncpy の違い

この2つはよく似ていますが、動きがかなり違います。
違いを表で整理すると、次のようになります。

項目strcpystrncpy
コピー方法\0 まで含めて全部コピー最大 n 文字までコピー
\0 の扱い必ずコピーする場合によっては付かない
使いどころ全体をそのままコピーしたいとき一部だけコピーしたいとき
注意点コピー先サイズ不足に注意\0 が付かない場合に注意

つまり、

  • 文字列全体をそのまま移したいなら strcpy
  • 文字数を制限してコピーしたいなら strncpy

という考え方が基本です。
ただし strncpy は「安全版 strcpy」と単純に考えると誤解しやすいので、\0 の扱いには特に注意が必要です。

strcpy と strncpy を使ったシンプルなプログラム例

元のメッセージをコピーし、さらに先頭の一部だけを別の配列へコピーするプログラムです。

ファイル名:12_5_2.c

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

int main(void)
{
    char original[] = "Programming";
    char full_copy[20];
    char part_copy[6] = "";

    /* 文字列全体をコピーする */
    if (sizeof(full_copy) >= strlen(original) + 1) {
        strcpy(full_copy, original);
        printf("全体コピーの結果: %s\n", full_copy);
    } else {
        puts("全体コピー用の配列が不足しています。");
    }

    /* 先頭5文字だけコピーする */
    strncpy(part_copy, original, 5);
    part_copy[5] = '\0';

    printf("一部コピーの結果: %s\n", part_copy);

    return 0;
}

このプログラムの見どころ

このプログラムでは、まず strcpy を使って Programming を full_copy に丸ごとコピーしています。
full_copy は20要素あるので、文字列本体と \0 を十分に格納できます。

次に、strncpy を使って先頭5文字だけを part_copy にコピーしています。
ただし、このままでは \0 が自動で付かない可能性を考えて、

part_copy[5] = '\0';

を自分で書いています。

この1行がとても大切です。
strncpy を使ったあとに文字列として安全にあつかうには、こうして終端をはっきり整える習慣が役立ちます。

バッファオーバーフローとは

文字列コピーで特に気をつけたいのが、バッファオーバーフローです。
これは、配列の大きさを超えてデータを書き込んでしまうことです。

たとえば、5文字しか入らない配列に、10文字以上の文字列を strcpy でコピーしようとすると、配列の外まで書き込んでしまうかもしれません。
その結果、プログラムが誤動作したり、異常終了したり、重大な不具合の原因になったりします。

バッファオーバーフローを防ぐための基本

対策内容
コピー先のサイズを確認する何文字入るかを事前に把握する
\0 のぶんも考える文字数ぴったりでは足りない
strncpy のあと終端を確認する文字列として使える状態か確認する
むやみにコピーしない必要な長さを意識する

C言語では、こうしたサイズ管理をプログラマ自身が意識することがとても大切です。

文字列操作関数を学ぶ意味

strlen、strcpy、strncpy は、どれも基本的な関数です。
でも、この3つを学ぶだけでも、C言語の文字列処理の考え方がかなり見えてきます。

この記事で身につくこと

学べること内容
文字列の正体文字列は文字配列と \0 でできている
長さの考え方\0 は文字数に含まれない
コピーのしくみ文字列は関数でコピーする
安全性の意識配列サイズや終端を考える必要がある
C言語らしさ自由度が高いぶん注意も必要になる

こうした基本がわかると、この先に出てくる strcmp、strcat、strchr、strstr などの関数も理解しやすくなります。

文字列操作関数を使うときの心構え

文字列操作関数はとても便利ですが、C言語では便利さの裏に「自分で注意するべき点」もあります。
特に大切なのは、次の3つです。

心がけ内容
文字列は \0 で終わる終端がないと文字列として正しくあつかえない
配列の大きさを意識するコピー先に十分な領域が必要
関数の性質を理解して使うstrcpy と strncpy は似ていても動きが違う

C言語の文字列処理は、最初は少し慎重さが必要です。
でも、そのぶん「文字列が実際にはどう保存され、どう動いているのか」がよく見えるようになります。
これはC言語を学ぶ大きな面白さのひとつです。

文字列操作関数を上手に使えるようになると、ただ文字を表示するだけでなく、文字列を組み立てたり、加工したり、判定したりするプログラムへと、できることが大きく広がっていきます。