C言語の標準関数を自作する:文字列処理 strncmp()編
目次
はじめに
C言語には、2つの文字列を比較するための標準関数として strncmp() が用意されています。
strcmp() が文字列全体を比較するのに対して、strncmp() は比較する最大バイト数を指定できる点が特徴です。
今回は、この strncmp() を標準Cライブラリの strncmp() を利用せずに自作します。
このプロジェクトでは、標準関数と同じインターフェースを提供するラッパー関数と、実際の処理を行うコア関数を分離する構成を採用しています。
今回作成する strncmp() は、以下のような構成とします。
利用側
↓
my_strncmp()
↓
core_strncmp()
my_strncmp() が利用側から呼び出され、実際の文字列比較処理は core_strncmp() が担当します。
なお、本プロジェクトでは、自作した標準関数模擬関数であるラッパー関数を、他の自作関数の実装から利用することを許容しています。
つまり、標準Cライブラリの関数を直接利用することは禁止しますが、自作した標準関数模擬関数(my_**()関数)については、必要に応じて他の自作関数から利用することができます。
これは、自作した標準関数群を組み合わせて利用できるようにするためのプロジェクト上の設計方針です。
1. strncmpの仕様を確認
まず、標準関数としての strncmp() の仕様を確認します。
Linux環境では、以下のコマンドでmanページを確認できます。
man strncmp
オンラインでは、Linux man-pages の strncmp(3) を参照できます。
1.1 関数プロトタイプ
strncmp() のプロトタイプは以下です。
int strncmp(const char *s1, const char *s2, size_t n);
strncmp() は、第1引数と第2引数で指定された2つの文字列を比較します。
ただし、strcmp() とは異なり、比較する最大バイト数を第3引数 n で指定できます。
例えば、
strncmp("Hello", "Hello", 5);
とした場合、最大5バイトを比較します。
また、
strncmp("Hello", "Hello World", 5);
では、最初の5バイトが一致するため、比較結果は 0 になります。
1.2 比較する最大バイト数
strncmp() の最大の特徴は、第3引数で比較する最大バイト数を指定できることです。
例えば、
strncmp("Hello", "Help", 3);
の場合、
H e l
H e l
の3バイトだけを比較します。
この3バイトは一致するため、戻り値は 0 になります。
一方、
strncmp("Hello", "Help", 4);
の場合は、
H e l l
H e l p
↑
4バイト目で比較結果が異なります。
このように、strncmp() では n の値によって、どこまで比較するかが決まります。
標準 strncmp() は、最大で n バイトを比較し、文字列中に '\0' が現れた場合には、それより後ろを比較しません。
1.3 戻り値
標準 strncmp() の戻り値は、
- 第1文字列が第2文字列より小さい場合は0未満。
- 2つの文字列が比較範囲内で一致する場合は0。
- 第1文字列が第2文字列より大きい場合は0より大きい。
となります。
例えば、
strncmp("ABC", "ABD", 3);
では、3文字目を比較すると、
A B C
A B D
↑
'C' < 'D' なので、戻り値は0未満になります。
逆に、
strncmp("ABD", "ABC", 3);
では、'D' > 'C' なので、戻り値は0より大きくなります。
重要なのは、標準 strncmp() では「負・0・正」という関係は保証されていますが、具体的な戻り値が -1 や 1 になるとは限らない点です。
例えば、実装によっては、
-1
0
1
となる場合もありますが、
-23
0
42
のようになる可能性もあります。
したがって、標準仕様としては戻り値そのものではなく、
< 0
== 0
> 0
という符号を利用する必要があります。
1.4 '\0' の扱い
strncmp() は最大 n バイトを比較しますが、文字列の途中で '\0' が出現した場合は、それ以降を比較しません。
例えば、
strncmp("ABC", "ABCDEF", 6);
の場合、
A B C \0
A B C D E F \0
となっています。
3文字目まで一致した後、第1文字列で '\0' が出現します。
つまり、
A B C \0
A B C D
↑
となり、
'\0' < 'D'
となるため、結果は 0未満 です。
そのため、それより後ろの E, F は比較されません。
これは、strncmp() が単純に「n バイト分のメモリを無条件に比較する関数」ではなく、文字列として比較を行う関数であるためです。
1.5 n == 0 の場合
n が0の場合、比較するバイト数は0です。
例えば、
strncmp("ABC", "XYZ", 0);
では、比較対象が存在しないため、比較結果は 0 になります。
したがって、自作関数でも、
n == 0 → 0
となるようにします。
n == 0 の場合は、文字列の内容を確認する必要もありません。
1.6 NULLポインタについて
標準 strncmp() に NULL ポインタを渡した場合の動作を、通常の仕様として扱うことはできません。
例えば、
strncmp(NULL, "ABC", 3);
のような呼び出しを、標準 strncmp() の正常な仕様として扱うことはできません。
今回の自作関数では、プロジェクト独自の安全性要件として、NULL を明示的に処理します。
2. 実装
ここまでで、標準 strncmp() の基本的な仕様を確認しました。
では、先程確認した仕様を参考に、実際に strncmp() を自作してみます。
今回のプロジェクトでは、利用側から呼び出されるラッパー関数と、実際の処理を行うコア関数を分離しています。
実装はこちらです。
2.1 コア関数のヘッダーファイル
まず、コア関数のインターフェースをヘッダーファイルに定義します。
#ifndef CORE_STRING_CORE_STRNCMP_H
#define CORE_STRING_CORE_STRNCMP_H
#include <stddef.h>
#include "myc/myc_define.h"
EXTERN int core_strncmp(
const char *ap_Source1,
const char *ap_Source2,
size_t av_Count);
#endif
第1引数と第2引数は比較対象となる文字列なので const char * とします。
第3引数は比較する最大バイト数なので size_t とします。
戻り値は比較結果を表す int とします。
ヘッダーファイルには、プロジェクトのコーディングルールに従い、インクルードガードを付けています。
インクルードガードは、同じヘッダーファイルが複数回インクルードされた場合に、定義が重複することを防ぐためのものです。
また、EXTERN はプロジェクト共通で使用するマクロです。
2.2 コア関数の実装
コア関数では、実際の文字列比較を行います。
#include <stddef.h>
#include "core/string/core_strncmp.h"
int core_strncmp(
const char *ap_Source1,
const char *ap_Source2,
size_t av_Count)
{
size_t lv_Index = 0;
unsigned char lv_Source1;
unsigned char lv_Source2;
if ((NULL == ap_Source1) && (NULL == ap_Source2)) {
return 0;
}
if (NULL == ap_Source1) {
return -2;
}
if (NULL == ap_Source2) {
return 2;
}
if (0 == av_Count) {
return 0;
}
while (lv_Index < av_Count) {
lv_Source1 = (unsigned char)ap_Source1[lv_Index];
lv_Source2 = (unsigned char)ap_Source2[lv_Index];
if (lv_Source1 < lv_Source2) {
return -1;
}
if (lv_Source1 > lv_Source2) {
return 1;
}
if ('\0' == ap_Source1[lv_Index]) {
return 0;
}
lv_Index++;
}
return 0;
}
この実装では、2つの文字列を先頭から順番に比較しています。
異なる文字を見つけた場合には、その時点で -1 または 1 を返します。
同じ文字が続いている場合は次の文字へ進みます。
また、'\0' に到達した場合には、その時点で比較を終了します。
さらに、lv_Index < av_Count という条件によって、最大 av_Count バイトまでしか比較しません。
av_Count が0の場合は、比較対象が存在しないため、文字列へアクセスせず 0 を返します。
2.3 ラッパー関数のヘッダーファイル
次に、利用側から呼び出されるラッパー関数のインターフェースを定義します。
#ifndef WRAPPER_STRING_MY_STRNCMP_H
#define WRAPPER_STRING_MY_STRNCMP_H
#include <stddef.h>
#include "myc/myc_define.h"
/*
* 標準関数: strncmp.
*
* 2つの文字列を指定された最大バイト数まで比較します.
*
* 引数:
* ap_Source1 : 比較対象の第1文字列.
* ap_Source2 : 比較対象の第2文字列.
* av_Count : 比較する最大バイト数.
*
* 戻り値:
* 第1文字列が小さい : -1.
* 文字列が等しい : 0.
* 第1文字列が大きい : 1.
* 第1引数がNULL : -2.
* 第2引数がNULL : 2.
*
* 標準strncmpとの相違点:
* 標準strncmpではNULLポインタを渡した場合の動作は保証されません.
* 本関数ではNULLを独自に処理します.
*
* また、標準strncmpは戻り値の符号のみを保証しますが、
* 本関数ではmy_strcmpと同じ戻り値体系を使用します.
*/
EXTERN int my_strncmp(
const char *ap_Source1,
const char *ap_Source2,
size_t av_Count);
#endif
こちらのヘッダーファイルにもインクルードガードを付けています。
また、利用側に対して標準 strncmp() と同じ引数の型を提供します。
2.4 ラッパー関数の実装
ラッパー関数では、利用側から呼び出される my_strncmp() を実装します。
#include "core/string/core_strncmp.h"
#include "wrapper/string/my_strncmp.h"
int my_strncmp(
const char *ap_Source1,
const char *ap_Source2,
size_t av_Count)
{
return core_strncmp(
ap_Source1,
ap_Source2,
av_Count);
}
ラッパー自身は比較処理を行わず、コア関数へ処理を委譲しています。
このように、今回の実装では、
my_strncmp()
↓
core_strncmp()
↓
最大nバイトまで文字列を比較
という役割分担になっています。
ここまでで、実際に動作する my_strncmp() の実装を確認しました。
ただし、このコードには、本プロジェクト独自の仕様やコーディング規約が反映されています。
🔗 関連記事・関連リンク
この記事の最新アップデートや、このシリーズの関連記事は以下のリンクからご覧いただけます。
内容は基本的に同じですので、お好みのサイトでお読みください。
🟢 Zenn
-
この記事の最新版はこちら。
-
「標準関数自作」Topicの関連記事はこちら。
🔵 Qiita
-
「標準関数自作」タグの関連記事はこちら。
🧡 note
-
このシリーズの記事はこちら。
-
要件定義以降は、有料パートとしてこちらで公開。