難しい wprintf

# wprintf の様々な問題

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

- setlocale による設定が必要

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

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


# wprintf テスト プログラム

1. setlocale なしで "", "test\n" を呼び出す
   - 以降、"test\n", "テスト\n", "「🈳」\n" の三つの出力を試みます
1. setlocale(LC_CTYPE, NULL) で試す
1. setlocale(LC_CTYPE, "") で試す
1. MSVC では
   - setlocale(LC_CTYPE, ".utf8") で試す
   - SetConsoleCP/SetConsoleOutputCP に CP_UTF8 を指定して試す
1. MSVC 以外では setlocale(LC_CTYPE, "en_US.UTF-8") で試す

マクロ USE_PRINTF が未定義だと printf を wprintf で出力するよう置き換えています。

```c++:
#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) になりつつも出力文字数が返される。


```console:実行結果(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" まで正しく出力された。

```console:実行結果(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)の文字はターミナルに表示される環境

```console:-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)
```

```console:-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)
```


# 参考

https://learn.microsoft.com/ja-jp/cpp/c-runtime-library/format-specification-syntax-printf-and-wprintf-functions

https://manpages.debian.org/bookworm/manpages-ja-dev/wprintf.3.ja.html