文字列の一部分を抜き出す | Programming Place Plus C言語編 逆引き

トップページC言語編逆引き

このページの概要

以下は目次です。

目的

ある文字列の中の一部分を抜き出した文字列を作りたいとします。対象の文字列の方は変化させません。

たとえば、“abcdef” の「2文字目から 3文字分を抜き出す」と、“cde” が得られます。

この手の処理は、プログラミング言語によっては標準で用意されていますが、C言語にはありませんから自作する必要があります。

以下のような関数にします。

/*
    文字列の一部分を抜き出した文字列を作成する

    引数
        target:      対象の文字列。
        begin:       抜き出す最初の文字は、target の先頭から何文字目か。
                     target の長さ以上を指定した場合の結果は、空文字列。
        length:      抜き出す文字数(末尾のヌル文字を含まない)
                     target の末尾に到達しても構わない。
        result:      結果を格納する配列のメモリアドレス。
                     ヌルポインタは不可。
                     末尾にヌル文字が付加される。
        result_size: result の要素数。length + 1 以上必要。
    戻り値
        result を返す。
*/
char* substring(const char* target, size_t begin, size_t length, char* result, size_t result_size);

次のように使います。

char result[4];
puts(substring("abcdef", 2, 3, result, sizeof(result)));    // "cde"

引数result_size はなくても実装できますが、バッファオーバーフローに備えられるようにするために、引数result の大きさをきちんと指定させるようにします。

文字列 s から 7文字抜き出すという指定をしたとき、文字列 s が “abcde” のように、そもそも 5文字しかなかったら、result は 6以上あれば十分なわけですが、必ず length + 1 以上の大きさを要求する仕様にしています。

result_size の大きさが、length + 1 以上でなければならないのは、length は末尾のヌル文字を含まずに指定しますが、result が指す配列には、ヌル文字を格納するための場所が必要であるためです。

方法①(strncpy関数を使って実装する)

ここでは、標準ライブラリの strncpy関数を使って実装してみます。

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

strncpy関数は、第1引数にコピー先、第2引数にコピー元、第3引数にコピーする長さ を指定します。

strncpy関数は、s2 + n の範囲でヌル文字が登場すれば、それも含めてコピーして終了します。しかし、ヌル文字が登場しない場合には、n の指定文字数分だけコピーを行い、末尾にヌル文字を付けません。この対策が必須です。

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

/*
    文字列の一部分を抜き出した文字列を作成する

    引数
        target:      対象の文字列。
        begin:       抜き出す最初の文字は、target の先頭から何文字目か。
                     target の長さ以上を指定した場合の結果は、空文字列。
        length:      抜き出す文字数(末尾のヌル文字を含まない)
                     target の末尾に到達しても構わない。
        result:      結果を格納する配列のメモリアドレス。
                     ヌルポインタは不可。
                     末尾にヌル文字が付加される。
        result_size: result の要素数。length + 1 以上必要。
    戻り値
        result を返す。
*/
char* substring(const char* target, size_t begin, size_t length, char* result, size_t result_size)
{
    assert(target != NULL);
    assert(result != NULL);
    assert(length + 1 <= result_size);

    if (begin < strlen(target)) {

        // target[begin] を起点に、length の文字数分だけコピー
        strncpy(result, target + begin, length);

        // 末尾のヌル文字を付加
        // strncpy() によってすでに付加されている可能性はある。
        // また、result[length] よりも手前にすでにヌル文字があるかもしれない。
        result[length] = '\0';
    }
    else {
        // begin が target の末尾以降の位置にあるときは、
        // 空文字列を返す
        result[0] = '\0';
    }

    return result;
}


int main(void)
{
    char result[4];

    puts(substring("abcdef", 0, 3, result, sizeof(result)));
    puts(substring("abcdef", 2, 3, result, sizeof(result)));
    puts(substring("abcdef", 2, 0, result, sizeof(result)));
    puts(substring("abcdef", 5, 3, result, sizeof(result)));
    puts(substring("abcdef", 7, 3, result, sizeof(result)));

    // result の要素数が足りない
//    puts(substring("abcdef", 2, 9, result, sizeof(result)));
}

実行結果:

abc
cde

f

strncpy関数の第2引数を target + begin とすることで、起点の位置を引数begin の指示に合わせて変更しています。この計算の結果、target の末尾を超えたところにまで進んでしまう恐れがあることに注意しなければなりません。そこで、直前の if文で、target の有効範囲内に収まっているかどうかを確認しています。収まっていなければ、else句のほうへ進み、空文字列を作って返します。

この部分で、strlen関数を使っているので、target の長さに応じたコストが掛かっています。

最初に説明したように、strncpy関数の実行を終えたあと、コピー先の文字列の末尾にはヌル文字がない可能性があります。そこで、result[length] = '\0'; として、必ず result の配列内にヌル文字が入っていることを保証するようにしています。

ところで、result[length] には、strncpy関数がコピーしたヌル文字が入っているかもしれません。また、4つ目の substring関数の呼び出しのように、コピーされた文字数が length の指定よりも小さい場合には、result[length] よりも手前の位置にヌル文字がすでにあるかもしれません。

こういったケースを判定することはできますが、特に確認せずに result[length] = '\0'; としています。result の長さが length + 1 以上であることを(result_size を正確に指定させることによって)保証しているので、この文が問題を起こすことはないはずです。


参考リンク


更新履歴



逆引きのトップページへ

C言語編のトップページへ

Programming Place Plus のトップページへ



はてなブックマーク に保存 Pocket に保存 Facebook でシェア
X で ポストフォロー LINE で送る noteで書く
rss1.0 取得ボタン RSS 管理者情報 プライバシーポリシー
先頭へ戻る