syghの新フラグメント置き場

プログラミングTipsやコード断片の保管場所です。お絵描きもときどき載せます。

MSVC (ユニバーサルCRT) のswprintfのバグ

Visual C++のC Runtime (CRT) ライブラリは、バージョン2015 (14.0) 以降、新しい設計のUniversal CRT (UCRT) を採用するようになり、C/C++標準ライブラリが再実装されました。
その際、かなりの数のバグが混入したのですが*1*2、いまだに修正されていないバグもいくつかあります。

例えば、以下のリファレンスでは、swprintf()WEOFを出力しようとした場合、戻り値はエラーコード-1を返すことが明記されており、サンプルコードでも例示されているのですが、UCRTでは間違った値を返します。

#include <stdio.h>

int main(void)
{
    wchar_t buf[100];
    int len = swprintf(buf, 100, L"%s", L"Hello world");
    printf("wrote %d characters\n", len);
    len = swprintf(buf, 100, L"%s", L"Hello\xffff world");
    // swprintf fails because string contains WEOF (\xffff)
    printf("wrote %d characters\n", len);
}

VC2013までは、11-1が正しく出力されるのですが、VC2015以降では1112が出力されます。
swprintf()系はvswprintf()系を使って実装されており、当然のごとくvswprintf()にも同じバグがあります。

これはWindows 10 21H1の最新版UCRT (ucrtbase.dll, 10.0.19041.789) でも修正されていません。

ちなみにWEOFは "%ProgramFiles(x86)%\Windows Kits\10\Include\10.0.*.0\ucrt\corecrt_wstdio.h" にて以下のように定義されています。

#define WEOF ((wint_t)(0xFFFF))

UCRTは、Windows 10では標準システムコンポーネント扱いとなっており、UCRTを動的リンクしたアプリケーションは、システムディレクトリにインストールされているDLLのほうを必ずロードするようになっています。Windows 8.xおよびそれ以前のバージョンでは、アプリケーションに同梱されているプライベートDLL(ローカルDLL)があればそちらを優先的にロードするようになっています。

UCRTのソースコードWindows SDKに付属しています。インターフェイスはCですが、内部実装はC++です。
swprintf()の定義を追っていけば、どのファイルに実装があるか分かりますが、どうも "%ProgramFiles(x86)%\Windows Kits\10\Source\10.0.*.0\ucrt\stdio\output.cpp" 内のcommon_vsprintf()の内部、processor_type::process()にバグがありそうです。processor_typeはテンプレートoutput_processorの特殊化ですが、その実装は "%ProgramFiles(x86)%\Windows Kits\10\Source\10.0.*.0\ucrt\inc\corecrt_internal_stdio_output.h" にあります。

余談ですが、C形式のAPIの内部実装にC++を使用することはよくあります。AndroidのBionic libcも、昔はすべてCとアセンブラで実装されていましたが、Android 4.xあたりで再設計されて部分的にC++で実装されるようになりました*3*4*5。なお、WindowsAndroidも、システム開発にRust言語の活用を始めているらしいので、将来的にCランタイムライブラリもRustで実装されることになるかもしれないですね。少なくともRustコードは教養として読めるようになっておいたほうがよさそうです。