strncpy
char* strncpy(char* dst, const char* src, size_t n);
(1) |
n バイト コピーする. |
(2) |
src の先頭からn バイトの部分にNUL文字(0x00 )がひとつもない場合,
dst は終端がNUL文字にならないことに注意が必要.
|
(3) |
文字列src の長さがn より短い場合,dst の残りの部分はNUL文字で塗りつぶされる.
したがって,dst の長さは少なくともn 以上でなければならない.
|
strcpy
とは根本的に違う動作になる
strncpy
はstrcpy
と違って
src
がNUL文字に到達しても終了せず,残りの部分の全バイトをNUL文字で塗りつぶすという仕様になっていることを覚えておこう.
普通のプログラマの認識ではstrcpy
とstrncpy
の違いは単にコピーする最大バイト数を意識するかどうかだけだと思われている
---したがって,src
のサイズがdst
のサイズより十分に小さい場合は同じ動作になると思われているが,実はそうではない.
strncpy
はsrc
側でNUL文字に到達したあとも
dst
側の残りの領域をすべて0x00
にするという余分な作業をやってくれている(図1).
図1 strcpy
とstrncpy
の動作の違い
|
16進値 |
dst[10] |
"ABCDEFGHI" |
41 42 43 44 45 46 47 48 49 00 |
src |
"123" |
31 32 33 00 |
strcpy(dst, src); の結果 |
31 32 33 00 45 46 47 48 49 00 |
strncpy(dst, src, 10); の結果 |
31 32 33 00 00 00 00 00 00 00 |
この動作の違いがどう影響するかだけど,まぁ,普通は違いが問題になることはないと思うけど…
(A1) |
性能要件がシビアなプログラムでstrcpy をstrncpy にかえると
余計な動作のために性能要件をクリアできなくなるかも.
たとえば図1の例と図2のような実装で考えると,
strncpy に変更すると比較回数が4回から4+10=14回に増加,メモリ転送回数が4回から10回に増加する.
比較命令を1クロック,メモリ転送命令を3クロックと仮定すると,処理時間は(14×1+10×3)÷(4×1+4×3)=2.75倍に増加することになり,
この劣化は簡単には受け入れられないと思う.
|
(A2) |
strncpy が余計な部分もゼロクリアしてくれることを前提にしている箇所では
strncpy とstrcpy は交換できない.
バイナリフォーマットでデータをやり取りするプログラムの場合(たとえばオブジェクトファイルを読み書きするようなプログラムとか),
フィールド長が何バイトでフィールド長より短い文字列を指定する場合は余った部分を0x00 で埋めることというような規則になっている場合が多く,
あながちトリッキーともいえない.
|
(A3) |
隠れてたバグが見つかるかも.
dst とsrc のサイズは同じつもりだったのに,実はdst の方が短かったというような場合に,
strcpy からstrncpy に変えて
サイズとしてsrc のサイズを与えたらセグメンテーション フォルトを起こしてしまったとか.
でも,これって別にstrcpy /strncpy の問題じゃないか.
|
図2 strcpyとstrncpyの実装イメージ
strcpy | strncpy |
while (*dst++ = *src++)
/* no body */;
return dst;
|
while (n-- > 0 && *dst++ = *src++)
/* no body */;
while (n-- > 0)
*dst++ = 0x00;
return dst;
|
文字列のサイズをMAX+1
のようにするのは誤解を生じやすいかも
文字列を保持する配列を用意する場合,次の(D1)(D2)の2種類の流儀があるが,
(D1)の流儀に統一しておくのがよさそうだ.
(D1) | (D2) |
#define BUFSIZE 10
char buf[BUFSIZE];
|
#define BUFSIZE 9
char buf[BUFSIZE + 1];
|
(D2)の意図は“文字列は最後にNUL
文字をいれるスペースが必要だから その分+1する”というもの.
ただ,こうすると,C言語の配列の慣習の枠組みから外れてしまって間違いが起こりやすい.
また,実際のプログラム例を検証してみると(表3),定義部でわざわざ+1としておいても
その後 手続き部を書くにあたって特にプログラムが読みやすくなるわけでも,間違いが防ぎやすいわけでもなさそう.
どうしても+1を明示したいなら,次の(D1.5)のような書き方がいいかもしれない.
[しかし10バイトの配列だからといって,必ず9文字+NUL
文字になってるわけではないんだよね….]
(D1.5) |
#define BUFSIZE (9 + 1)
char buf[BUFSIZE];
|
表3 よくある操作と書き方
操作 | 書き方 |
末尾要素へのアクセス |
buf[BUFSIZE - 1] |
for ループ |
for (i = 0; i < BUFSIZE; ++i) { ... } |
strncpy |
strncpy(buf, src, BUFSIZE); |
fgets |
fgets(buf, BUFSIZE, fp); |
C言語では文字列は単に文字の配列(char
型の配列)であり,いくつかの関数が文字0x00
を特別視するという約束事があるだけなので,
他の配列と同様に考えて,配列a[n]
,先頭a[0]
〜末尾a[n-1]
,
アクセスするときの慣例はfor (i = 0; i < n; ++i) ...
のように統一するほうがすっきりするように思う.
要するに文字列だけを特別視するのが混乱のものなんじゃないかな.
といいつつ,文字列は終端に0x00
をおくという規約がある以上,やっぱり特例では?という意見もあるだろうけど.
参考
はたいたかし
2007-04-16