1
0

難しい wprintf

Posted at

wprintf の様々な問題

出力先に合わせて文字コードの変換が入るので当然ですが

  • setlocale による設定が必要

様々な症状に出会します。

  • wprintf の戻り値が正常でも errno が設定されることがある
  • wprintf の戻り値が正常かつ errno も設定されなくても正常出力されないことがある
  • wprintf の戻り値が -1 でも正常出力されることがある
  • printf と wprintf の混在ができないことがある

wprintf テスト プログラム

  1. setlocale なしで "", "test\n" を呼び出す
    • 以降、"test\n", "テスト\n", "「🈳」\n" の三つの出力を試みます
  2. setlocale(LC_CTYPE, NULL) で試す
  3. setlocale(LC_CTYPE, "") で試す
  4. MSVC では
    • setlocale(LC_CTYPE, ".utf8") で試す
    • SetConsoleCP/SetConsoleOutputCP に CP_UTF8 を指定して試す
  5. 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)

参考

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0