<string.h> のどの関数も、ヌル終端文字で止まる char 配列上の小さなループにすぎません。それがはっきりすれば、このライブラリは謎めいた名前の一覧に見えるのをやめ、自分でも書いたであろう数個のループに見えてきます。このページは実際の仕事のほぼすべてを担うものを扱い、それらがバッファの末尾を越えて書かないようにするサイズの規則を示します。
ここのすべてに1つのインクルードが必要です。
#include <string.h>
strlen:文字数はいくつか
strlen は終端文字を含めずにそこまでのバイト数を数えます。size_t を返すので %zu で表示します。
strlen は 6、sizeof は 32 です。前者はテキスト、後者は記憶領域です - この区別がなぜそれほど重要かは文字列を参照してください。
strlen は呼ばれるたびに文字列全体を歩くので、変わらない同じ文字列に対するループの条件に置いてはいけません。
/* 毎周、文字列全体を数え直す */
for (size_t i = 0; i < strlen(s); i++) { ... }
/* 一度だけ数える */
size_t n = strlen(s);
for (size_t i = 0; i < n; i++) { ... }
strcpy と strncpy:コピー
strcpy(dst, src) は文字と終端文字をコピーします。dst の大きさをまったく知らないので、dst が少なくとも strlen(src) + 1 バイトを持つことを呼び出し元が保証しなければなりません。
strncpy は最大数を取りますが、有名な罠があります。コピー元が n バイト以上の長さなら、ちょうど n 文字をコピーし終端文字をコピーしません。必ず自分で終端させましょう。
sizeof dst - 1 に明示的な終端を添えるのが、覚えるべきパターンです。(strncpy はまた、短いコピー元に対して n バイトまでゼロで埋めます。大きなバッファでは無駄な作業です - 安全のためではなく、固定幅のレコードのために設計されたものなのです。)
strcat と strncat:連結
strcat(dst, src) は dst にすでに入っているものの末尾に src を追加します。格納先は両方の文字列と終端文字1つを保持できなければならず、またすでに妥当な文字列を含んでいなければなりません - 未初期化のメモリへの追加は未定義動作です。
char path[64]; ではなく char path[64] = "/home/ada"; である点に注意してください - 初期化子こそが、最初の strcat にとってこれを妥当な文字列にしているのです。
strncat(dst, src, n) は最大 n 文字を追加し、必ず終端文字を付けます。したがって n はバッファ全体の大きさではなく、残っている空き容量です。
strncat(dst, src, sizeof dst - strlen(dst) - 1);
strcat を繰り返し呼ぶと、そのたびに末尾を見つけるため格納先を走査し直します。そうやってループで長い文字列を作ると2乗の計算量になります。ある程度の大きさのものには、自分で書き込み位置を管理するか、文字列と数値の変換で扱う snprintf を使いましょう。
strcmp:比較
== はアドレスを比較するので、同じテキストを保持する別々の2つの配列に対して偽になります。strcmp は文字を比較し、差の符号を返します。
3つの規則です。
- 等価の判定には
strcmp(a, b) == 0を使いましょう。if (strcmp(a, b))と書くと「異なるなら」という意味になり、ほとんどの人には逆に読めます。 - 定義されているのは符号だけです。結果を
1や-1と比べてはいけません。 - 順序はバイト値によるので、ASCIIでは
"Zebra"が"apple"より前に並びます。大文字小文字を無視した比較には、まず両方のコピーを小文字にしましょう - 広く見かけるstrcasecmpはPOSIXの拡張であって標準Cではありません。
strncmp(a, b, n) は最初の n 文字だけを比較します。接頭辞を調べるきれいな方法です。
strchr と strstr:検索
strchr(s, ch) は文字の最初の出現を見つけ、strrchr は最後の出現を見つけます。strstr(haystack, needle) は部分文字列を見つけます。3つとも元の文字列の中を指すポインタを返し、一致がなければ NULL を返します。
返されたポインタから先頭を引けば添字が得られます。その型は ptrdiff_t で、%td で表示します。結果は元の配列の内側を指しているので、at + 1 はコピーをまったくせずに文字列の残りを表します - Cでとてもよくある書き方です。
参照外しの前に必ず NULL を確認しましょう。@ のない文字列に対する strchr(email, '@') + 1 は NULL からアドレスを計算しており、そのプログラムはもはや定義されていません。
文字列を分ける
分割の関数はありませんが、strchr と終端文字でその場で仕事ができます。この例はメールアドレスをユーザー名とドメインに切り分けます。
memcpy は終端文字を気にせず正確なバイト数をコピーします。長さがすでに分かっているときに欲しいものです。終端文字はそのあと手で書きます。
実例:名前を整える
これまでを組み合わせて - 空白を落とし、比較し、コピーし、つなぎます。
どの書き込みも格納先自身の sizeof で上限が定まっており、どの strncpy のあとにも明示的な終端が続いています。その規律を一貫して適用することが、C言語の文字列コードを安全にします。
覚えておくサイズの規則
strcpyは格納先にstrlen(src) + 1バイトの空きを必要とします。strcatはstrlen(dst) + strlen(src) + 1を必要とします。strncpyのあとは自分でdst[n - 1] = '\0'と書きましょう。strncatでは、数えるのは残っている空き容量です:sizeof dst - strlen(dst) - 1。strchrとstrstrの結果は、使う前にNULLかどうか確認しましょう。sizeof dstを使うのはdstが本物の配列のときだけです。char *dstを取る関数の中ではsizeofはポインタの大きさを返します - バッファの長さは別の引数として渡しましょう。
よくある質問
C言語の strcmp は何を返しますか?
2つの文字列が同一ならゼロ、1つ目が辞書順で先なら負の値、後なら正の値です。意味があるのは符号だけで、-1 や 1 を返すと仮定してはいけません。欲しい判定はほぼ常に if (strcmp(a, b) == 0) です。
なぜC言語では == で文字列を比較できないのですか?
a == b は文字ではなく2つのアドレスを比較するからです。"cat" を保持する別々の2つの配列は異なるアドレスにあるので、テキストが一致していても比較は偽になります。<string.h> の strcmp(a, b) == 0 を使いましょう。
strcpy と strncpy の違いは何ですか?
strcpy は格納先の大きさをまったく知らないまま、コピー元の '\0' までコピーします。strncpy は最大 n バイトで止まります - しかしコピー元がそれだけ長ければ終端文字をコピーしませんので、自分で dst[n - 1] = '\0'; と書かねばなりません。どちらも自動的に安全ではありません。サイズの規律はあなたのものです。
C言語で部分文字列を見つけるには?
strstr(haystack, needle) が最初の出現へのポインタを返し、なければ NULL を返します。元の文字列の中を指すポインタを返すので、found - haystack で添字が得られます。1文字を探すときは strchr を使いましょう。