strncpyとfgets
今更ですが、Cの文字列を扱う標準関数についてメモ。
strncpy(char *dst, const char *src, size_t n)とは、
- srcの指すアドレスからnバイト分の領域Aを、dstの指すアドレスから始まる領域Bにコピーする
- ただし、Aの途中で'\0'があった場合はそれ以降のBは'\0'で埋める
- コピー元Aとコピー先Bの領域がダブった場合の挙動は未定義
です。これが何を意味するかというと、
- srcの指すアドレスからnバイト以内(領域A)に'\0'が無いと、strncpyは領域Bに'\0'を書き込まない
- dstの指すアドレスからnバイト(領域B)には必ず何かが書き込まれる
すなわち、srcの指すアドレスから始まる「文字列」の長さが不明の場合に、strncpy()を安全に使おうと思ったら、
- dstの指すアドレスから始まる領域には、n+1バイト(以上)を確保しておく
- dst[n]には'\0'を入れておく
必要があります。まず、少なくとも領域Bにはnバイトを確保していないと領域破壊が起こりますし、n+1番目すなわちdst[n]に'\0'を入れておかないと、領域Bの'\0'終端が保証されません。ですので結局、領域Bには末尾の'\0'を含めてn+1バイトが必要になります。
注目すべきはfgets( char *string, int n, FILE *stream)との哲学の違いです。上記のとおり、strncpy()は、nバイトコピーすることに一生懸命で、'\0'終端することにはあまり興味がありません(途中で'\0'に出会うと、それ以降'\0'でコピーしますが、これもよく考えると謎な仕様ですね)。一方fgetsは「Cでは文字列は'\0'で終端されていなければならないので、fgetsが書き込む領域はfgets自身が必ず'\0'で終端」します。fgetsは、streamから1行分、すなわち'\nまで読み込んでstringから始まる領域に書き込もうとしますが、上限のサイズnが与えられています。この上限値を使うやり方としては、
- 最大n文字を書き込んで、n+1文字目に'\0'を書く(=stringにはn+1バイトの確保が必要)
- 最大n-1文字を書き込んで、n文字目に'\0'を書く(=n-1文字しか取得されない)
の2通りがあるかと思います。一長一短がありますが、実際のfgetsは、おそらく領域破壊の可能性が低いと考えたのでしょう、「2.」の仕様になっています。
サンプルはこんな感じ
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdlib.h>
#define STR_LENGTH 1023
#define BUFFER_LENGTH 1023
void get_str(char*, int);
int main(void)
{
char str_array[STR_LENGTH + 1];
memset(str_array, '\0', sizeof(str_array));
get_str(str_array, sizeof(str_array) - 1); //領域のサイズ-1がミソ
printf("%s\n", str_array);
exit(0);
}
/* p_strで指すアドレスからsize_of_strバイトの領域に書き込む */
void get_str(char *p_str, int size_of_str)
{
char buff[ BUFFER_LENGTH + 1 ];
memset(buff, '\0', sizeof(buff)); // この初期化は厳密には不要
FILE *fp = NULL;
fp = fopen("hoge.txt", "r");
fgets(buff, sizeof(buff), fp); // fgetsはsizeof(buff)でOK
strncpy(p_str, buff, size_of_str); // buffのサイズではない!
}
うーん、配列宣言でよくあるarray[ LENGTH + 1 ]の「+ 1」の意義ってあまり上手く説明できないなぁ。仕様上の最大文字数+'\0'の1バイトって意味なんだろうけど。。はじめっから「仕様上の最大文字数 + 1」を#defineしとけばいいじゃんって気もするし。単なる慣習ってことかしら。
では、また。
[追記] ソース、やっぱりバグってたので、コンパイルが通る程度に修正しました。
| 固定リンク
この記事へのコメントは終了しました。


コメント