« 今年の目標 | トップページ | 成功本50冊「勝ち抜け」案内 »

2008年1月 9日 (水)

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が与えられています。この上限値を使うやり方としては、

  1. 最大n文字を書き込んで、n+1文字目に'\0'を書く(=stringにはn+1バイトの確保が必要)
  2. 最大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しとけばいいじゃんって気もするし。単なる慣習ってことかしら。

では、また。

[追記] ソース、やっぱりバグってたので、コンパイルが通る程度に修正しました。

|

« 今年の目標 | トップページ | 成功本50冊「勝ち抜け」案内 »

コメント

コメントを書く



(ウェブ上には掲載しません)




トラックバック

この記事のトラックバックURL:
http://app.cocolog-nifty.com/t/trackback/207693/17634908

この記事へのトラックバック一覧です: strncpyとfgets:

« 今年の目標 | トップページ | 成功本50冊「勝ち抜け」案内 »