wprintf の様々な問題
出力先に合わせて文字コードの変換が入るので当然ですが
- setlocale による設定が必要
様々な症状に出会します。
- wprintf の戻り値が正常でも errno が設定されることがある
- wprintf の戻り値が正常かつ errno も設定されなくても正常出力されないことがある
- wprintf の戻り値が -1 でも正常出力されることがある
- printf と wprintf の混在ができないことがある
wprintf テスト プログラム
- setlocale なしで "", "test\n" を呼び出す
- 以降、"test\n", "テスト\n", "「🈳」\n" の三つの出力を試みます
- setlocale(LC_CTYPE, NULL) で試す
- setlocale(LC_CTYPE, "") で試す
- MSVC では
- setlocale(LC_CTYPE, ".utf8") で試す
- SetConsoleCP/SetConsoleOutputCP に CP_UTF8 を指定して試す
- MSVC 以外では setlocale(LC_CTYPE, "en_US.UTF-8") で試す
マクロ USE_PRINTF が未定義だと printf を wprintf で出力するよう置き換えています。
#include <stdarg.h>
#include <stdio.h>
#include <wchar.h>
#include <locale.h>
#include <errno.h>
#ifdef _MSC_VER
#include <Windows.h>
#define USE_PRINTF 1
#endif
#ifndef USE_PRINTF
// printf,wprintf 混在不可対策
#define printf nprintf
static void printf(const char *fmt, ...)
{
size_t len = 1024;
char nbuf[len];
wchar_t wbuf[len];
va_list ap;
va_start(ap, fmt);
vsnprintf(nbuf, len, fmt, ap);
va_end(ap);
for (size_t i = 0; i < len; ++i)
if (!(wbuf[i] = nbuf[i]))
break;
wprintf(L"%ls", wbuf);
}
#endif
void test(const char* locale)
{
static struct {
int res, err;
const wchar_t *msg;
} data[] = {
{ 0, 0, L"test\n" },
{ 0, 0, L"テスト\n" },
{ 0, 0, L"「🈳」\n" },
{ 0, 0, NULL },
};
printf("--------------------\n");
printf("setlocale(LC_CTYPE, \"%s\")\n", locale);
setlocale(LC_CTYPE, locale);
printf("LC_TYPE: %s\n", setlocale(LC_CTYPE, NULL));
for (int i = 0; data[i].msg; ++i)
{
errno = 0;
printf("test%d:\n", i);
data[i].res = wprintf(data[i].msg);
data[i].err = errno;
}
printf("\n");
for (int i = 0; data[i].msg; ++i)
printf("res%d = %d (errno:%d)\n", i, data[i].res, data[i].err);
}
int main()
{
printf("EILSEQ = %d\n", EILSEQ);
printf("--------------------\n");
printf("test0:\n"); int res0 = wprintf(L"");
printf("test1:\n"); int res1 = wprintf(L"test\n");
printf("res0 = %d\n", res0);
printf("res1 = %d\n", res1);
printf("\n");
test(NULL);
test("");
#ifdef _MSC_VER
test(".utf8");
printf("Console -> CP_UTF8\n");
SetConsoleCP(CP_UTF8);
SetConsoleOutputCP(CP_UTF8);
test(NULL);
#else
test("en_US.UTF-8");
#endif
return 0;
}
実行結果
試しているのは以下の三つ
- Windows 11
- macOS 14.5
- Debian 11.9
Windows 11
コンパイラ: MSVC v143 - VS 2022 C++
printf/wprintf 混在でビルドされます。
"テスト\n" が出力されるには
- setlocale(LC_CTYPE, "") // Japanese_Japan.932
- setlocale(LC_CTYPE, ".utf8")
のどちらかが必要。
"「🈳」\n" では
- setlocale(LC_CTYPE, ".utf8")
のとき、恐らく "🈳" が "" (errno=EILSEQ) になりつつも出力文字数が返される。
実行結果(MSVC)
EILSEQ = 42
--------------------
wprintf[0]:
wprintf[1]:
test
res0 = 0
res1 = 5
--------------------
setlocale(LC_CTYPE, "(null)")
LC_TYPE: C
wprintf[0]:
test
wprintf[1]:
wprintf[2]:
res0 = 5 (errno:0)
res1 = -1 (errno:42)
res2 = -1 (errno:42)
--------------------
setlocale(LC_CTYPE, "")
LC_TYPE: Japanese_Japan.932
wprintf[0]:
test
wprintf[1]:
テスト
wprintf[2]:
「
res0 = 5 (errno:0)
res1 = 4 (errno:0)
res2 = -1 (errno:42)
--------------------
setlocale(LC_CTYPE, ".utf8")
LC_TYPE: Japanese_Japan.utf8
wprintf[0]:
test
wprintf[1]:
テスト
wprintf[2]:
「」
res0 = 5 (errno:0)
res1 = 4 (errno:0)
res2 = 5 (errno:42)
Console -> CP_UTF8
--------------------
setlocale(LC_CTYPE, "(null)")
LC_TYPE: Japanese_Japan.utf8
wprintf[0]:
test
wprintf[1]:
テスト
wprintf[2]:
「」
res0 = 5 (errno:0)
res1 = 4 (errno:0)
res2 = 5 (errno:42)
macOS 14.5
コンパイラ: Apple clang version 15.0.0
コンパイル時に -DUSE_PRINTF を設定しています。一度でも、wprintf で EILSEQ が発生すると戻り値は -1 に固定されてしまう。しかし、LC_CTYPE の文字種が UTF-8 だと "「🈳」\n" まで正しく出力された。
実行結果(macOS)
EILSEQ = 92
--------------------
wprintf[0]:
wprintf[1]:
test
res0 = 0
res1 = 5
--------------------
setlocale(LC_CTYPE, "(null)")
LC_TYPE: C
wprintf[0]:
test
wprintf[1]:
wprintf[2]:
res0 = 5 (errno:0)
res1 = -1 (errno:92)
res2 = -1 (errno:92)
--------------------
setlocale(LC_CTYPE, "")
LC_TYPE: en_US.UTF-8
wprintf[0]:
test
wprintf[1]:
テスト
wprintf[2]:
「🈳」
res0 = -1 (errno:0)
res1 = -1 (errno:0)
res2 = -1 (errno:0)
--------------------
setlocale(LC_CTYPE, "en_US.UTF-8")
LC_TYPE: en_US.UTF-8
wprintf[0]:
test
wprintf[1]:
テスト
wprintf[2]:
「🈳」
res0 = -1 (errno:0)
res1 = -1 (errno:0)
res2 = -1 (errno:0)
Debian 11.9
コンパイラ: g++ (Debian 10.2.1-6) 10.2.1 20210110
- コンパイル時に -DUSE_PRINTF がある場合
- printf の後の wprintf は何も出力しない
- コンパイル時に -DUSE_PRINTF がない場合
- "テスト", "「🈳」" の各文字は '?' に化けた
- 出力文字数は正で、errno は 0 なのでエラー扱いにはなっていない
- 何かの設定次第で正常化しそうな気はするが、何が悪いのか分からない
- ソースコード(UTF-8)の文字はターミナルに表示される環境
- "テスト", "「🈳」" の各文字は '?' に化けた
-DUSE_PRINTFあり
EILSEQ = 84
--------------------
wprintf[0]:
wprintf[1]:
res0 = -1
res1 = -1
--------------------
setlocale(LC_CTYPE, "(null)")
LC_TYPE: C
wprintf[0]:
wprintf[1]:
wprintf[2]:
res0 = -1 (errno:0)
res1 = -1 (errno:0)
res2 = -1 (errno:0)
--------------------
setlocale(LC_CTYPE, "")
LC_TYPE: en_US.UTF-8
wprintf[0]:
wprintf[1]:
wprintf[2]:
res0 = -1 (errno:0)
res1 = -1 (errno:0)
res2 = -1 (errno:0)
--------------------
setlocale(LC_CTYPE, "en_US.UTF-8")
LC_TYPE: en_US.UTF-8
wprintf[0]:
wprintf[1]:
wprintf[2]:
res0 = -1 (errno:0)
res1 = -1 (errno:0)
res2 = -1 (errno:0)
-DUSE_PRINTFなし
EILSEQ = 84
--------------------
wprintf[0]:
wprintf[1]:
test
res0 = 0
res1 = 5
--------------------
setlocale(LC_CTYPE, "(null)")
LC_TYPE: C
wprintf[0]:
test
wprintf[1]:
???
wprintf[2]:
???
res0 = 5 (errno:0)
res1 = 4 (errno:0)
res2 = 4 (errno:0)
--------------------
setlocale(LC_CTYPE, "")
LC_TYPE: en_US.UTF-8
wprintf[0]:
test
wprintf[1]:
???
wprintf[2]:
???
res0 = 5 (errno:0)
res1 = 4 (errno:0)
res2 = 4 (errno:0)
--------------------
setlocale(LC_CTYPE, "en_US.UTF-8")
LC_TYPE: en_US.UTF-8
wprintf[0]:
test
wprintf[1]:
???
wprintf[2]:
???
res0 = 5 (errno:0)
res1 = 4 (errno:0)
res2 = 4 (errno:0)
参考