The Man Who Fell From The Wrong Side Of The Sky:2007年4月3日分

2007/4/3(Tue)

[C言語] scanf(3)

scanf/wscanfファミリーを使うときのちょっとした注意おば。

@ scanf + "%c" + field width

これは大丈夫。

#include <stdio.h>

int main(void)
{
	char buf[3];
	int n;

	scanf("%3c%n", &buf[0], &n);
	printf("%.*s", n, buf);

	return 0;
}
width = byte数(char)

の計算ですので、bufの長さはwidthと同じだけ用意すればOK。
stdinからは(EOFに達しない限り)最大width=3byte読み込まれ、bufに書き込まれます。

@ wscanf + "%lc" + field width

これも大丈夫。

#include <locale.h>
#include <stdio.h>
#include <wchar.h>

int main(void)
{
	wchar_t buf[3];
	int n;

	setlocale(LC_CTYPE, "ja_JP.eucJP");
	wscanf(L"%3lc%n", &buf[0], &n);
	wprintf(L"%.*ls\n", n, buf);
	return 0;
}
width == ワイド文字数(wchar_t)

の計算で、 bufも同じ長さだけ確保。
stdinからはfgetwc(3)相当を用いて(WEOFが返されない限り)
最大width=3ワイド文字分読み込まれ、bufに書き込まれます。

@ scanf + "%lc" + field width

こいつは曲者です、実装により差があります。

  • FreeBSDと(その実装を先日取り込んだ)NetBSDの場合
    test.c
    ----------------------------------------
    #include <locale.h>
    #include <stdio.h>
    #include <string.h>
    #include <wchar.h>
    
    int main(void)
    {
    	wchar_t buf[3];
    	int n;
    
    	setlocale(LC_CTYPE, "");
    	memset(buf, 0, sizeof(buf));
    	scanf("%3lc%n", buf, &n);
    	wprintf(L"%d:%3ls\n", n, buf);
    
    	return 0;
    }
    ----------------------------------------
    $ make test
    
    としてできたバイナリを実行すると
    $ LC_CTYPE=ja_JP.eucJP echo "あいうえお" | ./test
    3:あ
    
    との結果が出力されます。
    width = 読み込むワイド文字数 ではないことに注意してください。
    あくまでstdinからは(EOFにならない限り)width=3byteだけ読み込むという意味になります。
    読み込まれたマルチバイト文字はmbrtowc(3)によってwchar_tに変換を試み、成功すればbufに書き込まれます。
    bufの長さはmb_cur_min=1を仮定してwidthと同じだけ確保が必要。

  • glibc2やVisualC/C++の場合
    [VisualC/C++]
    あいうえお
    6:あいう
    Press any key to continue
    
    [glibc2(FedoraCore6)]
    $ LC_CTYPE=ja_JP.eucJP echo "あいうえお" | ./test
    あいうえお
    6:あいう??????????
    
    glibc2のwprintf(3)バグってね?というのは置いといて(printfの方も%lsの動作がおかしい)
    width = 読み込むワイド文字数で、6byte stdinから読み込まれたことが判ります。

さてどちらが正しい実装なのでしょうか?
SUSv3を読む限り、前者の実装の方が正しいように私には思えます。

@ wscanf + "%c" + field width

こっちも困ったことに実装により差があります。
詳しくは 昨日のメモ以前のメモを参照してください。
どちらが正しいかといわれると、 SUSv3読む限りでは、今度は逆にglibc2とVisualC++の方が正しい実装のように思えます。
不幸なことに実装によって必要とされるbufの長さがまちまちなので
FreeBSDで書いたコードがLinuxではオーバーランなんて事態が起きる可能性があります。

@ 結論

C99拡張を取り込んでないOpenBSD最強
以上の通り実装によっててんでバラバラなんで
scanf + %lc、wscanf + %c をfield width付きで使うのは避けた方がいい鴨。
そもそもどちらもbufに何byte、何文字書かれたか判らないという欠点もあることだし。
その他のscanf(3)を使うときに注意する点は C FAQでも読んで頂戴。