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

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

歴史的偉業

松山英樹氏のマスターズ初優勝、おめでとうございます。日本人選手にとってもメジャー大会初優勝ということで、まさに歴史的偉業でした。めずらしく早朝からTVにかじりついてスポーツ観戦していたんですが、遠く離れた日本の地からでも歴史的瞬間に立ち会えた気がします。

自分も昔、祖父の影響を受けて多少遊び程度でゴルフをやっていたんですが、当時はちばてつや先生の漫画「あした天気になあれ」をむさぼるように繰り返し読んでいたことを懐かしく思います。この漫画の主人公・向太陽*1は、荒川の河川敷でプロゴルファーになることを夢見て日々ゴルフに明け暮れ、ついに地方予選を勝ち上がって全英オープンに初出場するのですが、さすがに漫画ならではのご都合主義的な展開があるものの、いつか日本人だってメジャー大会で優勝できる日が来るんだ、という夢を与えてくれるものでした。当時は現実のものとなる日がくるとは思っていませんでしたが、あれから30年を経て、オーガスタのフェアウェイを堂々闊歩する松山氏のプレーぶりと表情を見ていたら、なるほどこれは優勝してもまったくおかしくないはずで、彼の努力の賜物なのだと、あらためて敬服しました。

政治の道具に使われるばかりで利権まみれのオリンピックなんぞにはまったく興味ありませんが、一方でつまらないしがらみのない世界大会における一流選手同士の対戦は、やはり観ていてわくわくしますね。今回のTBS放送は解説も紳士的でした。

贅沢を言うようですが、次はぜひとも、全英オープンしかもセントアンドリュースでの優勝を見てみたいものです。

*1:太陽は幼い頃に父親を亡くし、大衆食堂を営む母親に女手一つで育てられるのですが、弟二人と妹一人がいて、家計を助けるために中学を卒業した後はプロゴルファーの試験を受けながらゴルフ場で働くなど、苦労を重ねていました。持ち前の前向きで明るい性格やチャレンジャー精神、小柄だけど逞しい体に秘めたパワーを武器に、支えてくれる家族や幼馴染の助け、師匠や先輩の厳しい教え、そして心強いキャディーを得て、成長していく物語です。昭和末期ののどかな風景が広がる日常の描写も印象的でした。

コロナ対策(笑)がアホすぎてめまいがする

飲食店の時短営業の強制とか、収支の実績無視で一律の給付金とか、もういい加減やめたら?
もっとも必要なのは人数制限でしょうが。もうアホすぎてめまいがする。そんで政治の中枢にいるゴミカスどもは今日も今日とてコソコソ集まって会食してるんでしょ?

利権にまみれたゴキブリどものたかるクソ五輪ごときに費やす金があったら、本物の復興支援に回せばいいのに。こんな状況で、所詮プロパガンダのためでしかない福島発の聖火リレーを結局ゴリ押しスタートとか、冗談にもほどがある。これが日本かと。恥ずかしくて情けない。結局、先の大戦から75年以上経っても、日本人は何も学んでいないことがハッキリしました。

もうね、自民党とか、自民党に投票する連中とか、クソ五輪推進派とか、クソ原発推進派とか、一度まとめて全員死んだほうがいいと思いますよ。自民党もクズですが、その自民党に投票する連中もクズです。先の大戦では暴走した軍部もクズでしたが、彼我の国力と戦力差すら客観的に分析・判断できず、それを抑えることができなかった政治家も昭和天皇も能無しで、マスゴミに踊らされて右にならえで大日本帝国バンザイ、天皇陛下バンザイを叫んだ国民もバカだったんです。

シン・エヴァ観てきました【微妙にネタバレ有り】

有給休暇をとって、平日の人が少ない時間帯を狙って「シン・エヴァンゲリオン劇場版:||」を観てきました。まだ鑑賞できていない人も沢山いると思うので、物語の核心に詳しく触れるような内容は避けて、ザックリとした感想だけ述べることにします。鑑賞前に一切の先入観を持ちたくない人はただちにブラウザを閉じて劇場へどうぞ。

続きを読む

BSTRはwchar_tへのポインタではありません

COMの文字列にはBSTRというデータ型が使われています。
BSTRが定義されているWindows SDKのヘッダーを見てみましょう。

// <wtypes.h> in Windows SDK 8.1

#ifndef _PREFAST_
typedef /* [wire_marshal] */ OLECHAR *BSTR;
#else // _PREFAST_
typedef _Null_terminated_ OLECHAR *BSTR;
#endif

OLECHARはもともとCOMの前身であるOLE (Object Linking and Embedding) で使われていた文字型の名残で、当時はANSI MBCSのcharでしたが、現在はWCHARすなわちwchar_tと等価です。Windows上ではsizeof(wchar_t) == 2であり、UTF-16エンコーディングUnicode文字を表現するのに使われます。

// <WTypesbase.h> in Windows SDK 8.1

#if defined(_WIN32) && !defined(OLE2ANSI)
typedef WCHAR OLECHAR;
typedef /* [string] */  __RPC_string OLECHAR *LPOLESTR;
typedef /* [string] */  __RPC_string const OLECHAR *LPCOLESTR;
#define OLESTR(str) L##str
#else
typedef char      OLECHAR;
typedef LPSTR     LPOLESTR;
typedef LPCSTR    LPCOLESTR;
#define OLESTR(str) str
#endif

「ふーん、じゃあBSTRwchar_tへのポインタなんだね。てことは、BSTRを要求するAPIにはwchar_tへのポインタを渡してもいいんだ!」

ダメです。絶対にやめてください。

そもそもBSTRとは?

BSTRはCOMだけでなく旧VB/VBAでも使われている、長さ情報を持った自己記述的文字列型の一種です。Windows SDKの <oaidl.h> で定義されているVARIANT型とも深い関わりがあります。
BSTRのデータ構造に関しては、以下の資料をよく読めばイメージできるはずです。

実際にコードで試してみます。

#define NOMINMAX
#include <Windows.h>
#include <tchar.h>
#include <cstdio>
#include <clocale>
#include <initializer_list>

int main() {
    _tsetlocale(LC_ALL, _T(""));

    for (auto str : { L"abc", L"hoge", L"Hello" }) {
        // BSTR の型自体は OLECHAR* すなわち wchar_t* の typedef エイリアスだが、
        // 実際の BSTR データ構造は先頭に4バイトの長さ情報(符号無し整数)を持っている。
        // Pascal の自己記述型文字列と、C の NUL 終端文字列の両方をミックスしたような構造。

        // SysAllocString() は、終端 NUL 文字の分を加味した (4 + UTF-16 文字列バイト長 + 2) の長さを持つバイト配列を確保し、
        // 5バイト目以降へのポインタを BSTR 型として返却しているにすぎない。
        // SysFreeString() は、渡されたポインタからマイナス方向に4バイトオフセットした位置を先頭とするバイト配列を解放する。
        // SysStringByteLen() や SysStringLen() は、渡されたポインタからマイナス方向に4バイトオフセットした位置に長さ情報が格納されていると仮定して値を読み取っている。
        // これにより、SysStringByteLen() と SysStringLen() の計算量オーダーは O(1) となる。
        // したがって、BSTR を要求する API に wchar_t* を渡してはいけない。
        // const wchar_t* や const wchar_t[] を BSTR にキャストするのもダメ。
        BSTR bstr = ::SysAllocString(str);
        const UINT sizeInBytes = *(reinterpret_cast<UINT32*>(bstr) - 1);
        const UINT sysByteLen = ::SysStringByteLen(bstr);
        const UINT sysStrLen = ::SysStringLen(bstr);
        wprintf(L"BSTR: wsz = \"%ls\", size = %u[bytes], byte-len = %u, len = %u\n", bstr, sizeInBytes, sysByteLen, sysStrLen);
        ::SysFreeString(bstr);
        bstr = nullptr;
    }
    // BSTR を偽装してみる。通常はマネしないように。
    // 本来の BSTR データ構造であれば、リトルエンディアンのバイト長として (2 * 3) == 6 が格納されていなければならない。
    // また、バイト長は2の倍数すなわち偶数でなければならない。
    struct { INT32 len; wchar_t str[128]; } dummyData = { 65535, L"abc" };
    BSTR bstrIllegal = dummyData.str;
    const UINT sysByteLen = ::SysStringByteLen(bstrIllegal);
    const UINT sysStrLen = ::SysStringLen(bstrIllegal);
    wprintf(L"Illegal BSTR: byte-len = %u, len = %u\n", sysByteLen, sysStrLen);
}

以上のように、BSTRはメモリの確保/解放や長さ取得に専用のAPIを利用します。
BSTRをC/C++で読み取る際はwchar_tのゼロ終端文字列として扱うこともできますが、その逆は不可です。決してwchar_tのゼロ終端文字列をBSTRとして扱ってはいけません。未定義の異常動作を引き起こす原因になります。

BSTRのラッパー

Visual C++ではATL::CComBSTR_bstr_tといったラッパークラス型(スマートポインタ)が用意されており、ライフサイクル管理の煩雑なBSTRを比較的簡単に扱うことができます。
WIL (Windows Implementation Library) にもwil::unique_bstrが用意されています。

ただし、BSTRラッパーの内部では結局システムAPIをコールするので、ヒープ管理にCRT (C Runtime) を利用するATL::CStringWや標準C++std::wstringと違い、速度やメモリ効率などのパフォーマンス面で不利です。
BSTRラッパーはBSTRを要求するCOMコンポーネントとの境界面 (API) にのみ使用し、普段は文字列の管理にATL::CStringWstd::wstringを使うべきです。

WindowsのOutputDebugString()関数とUnicode

WindowsOutputDebugStringW()関数とOutputDebugStringA()関数は、デバッグコンソールに文字列を出力します。

Visual Studioデバッグ実行すると、「出力ウィンドウ」に文字列が表示されます。
ただしAndroidのLogcatや、MacXcodeNSLogと違って、末尾に改行は自動付与されません。
DebugViewというツールを使うと、関数呼び出しごとの出力レコードをリスト表示することができます。Visual Studioデバッグ実行は動作が重たくなりがちですが、DebugViewはかなり軽快です(UIが古臭いのが欠点)。また、OutputDebugString()はリリースビルドでも使えるので、Visual Studioをインストールせずにデプロイ環境で手軽にログ出力しながら動作確認したいときに重宝します*1

ここで、

OutputDebugStringW converts the specified string based on the current system locale information and passes it to OutputDebugStringA to be displayed. As a result, some Unicode characters may not be displayed correctly.

と説明されているんですが、Visual Studio 2013までは確かに以下のようなコードは文字化け*2して日本語cR???となってしまうものの、Visual Studio 2015以降は正しく表示できています。

#include <Windows.h>

int main() {
    ::OutputDebugStringW(L"日本語©®™♡♥\n");
}

以下の説明から推察するに、VS2015ではWindows 10で追加されたAPIWaitForDebugEventEx()」をコールすることで、Unicode出力を実現しているようです。確かWindows 7/8.x上では、VS2015であってもOutputDebugStringW()Unicode固有文字を出力することはできていなかったように思います。

Important
In the past, the operating system did not output Unicode strings via OutputDebugStringW and instead only output ASCII strings. To force OutputDebugStringW to correctly output Unicode strings, debuggers are required to call WaitForDebugEventEx to opt into the new behavior. On calling WaitForDebugEventEx, the operating system will know that the debugger supports Unicode and is specifically opting into receiving Unicode strings.

ATLTRACE

ATL/MFCアプリやWinRTアプリでは、デバッグビルド時のみ有効になるATLTRACE()マクロ (atltrace.h) を使ってOutputDebugString()を間接的に利用することが多いですね。このマクロは第1引数に書式文字列を受け取るので便利です。MFCTRACE()マクロ (afx.h) は、ATLTRACE()と同じです*3

ただしVS2013 (ATL 12.0) 以降のATLTRACE()では、

hoge.cpp(123) : atlTraceGeneral - 出力メッセージ

というような感じで、ファイルパスと行番号と診断レベルが出力メッセージ先頭に付加されるようになりました。
Visual Studio IDEの出力ウィンドウでは、「FilePath(LineNumber):」の形で表示されたメッセージをダブルクリックすると、該当するソース行にジャンプできる機能が昔からあり、そのため __FILE____LINE__ を使って書式化されたメッセージを出力することがよくあったんですが、特にユーザーコードで工夫するまでもなく自動的にこの機能が利用できる形になりました。
ただ、常時出力されてしまうため、情報量が増えることでノイズも増え、メッセージ本体が埋もれてしまってうっとうしいと感じることもあります。

System.Diagnostics.Debug

.NETのDebug.WriteLine()メソッドやDebug.Print()メソッドは、VS2012/VS2013でもUnicode固有文字を出力できました。

System.Diagnostics.Debug.WriteLine("日本語©®™♡♥");

System.Diagnostics.Debugも内部的にOutputDebugStringW()を使っていると思っていたんですが、以下の解説によると、マネージコードに関しては別のデバッグエンジンが使われるため、OutputDebugStringW()の問題を回避できるようです。

Note
Visual Studio includes its own debugging environment and debugging engine, which together are called the Visual Studio debugger. For information on debugging in Visual Studio, see Debugging in Visual Studio. For debugging managed code, such as C#, using the Visual Studio debugger is often the easiest way to get started.

記憶が定かではないのですが、.NETのほうは確かWindows 10だけでなく、Windows 7/8.xでもUnicode固有文字を出力できていたような気がします。

*1:逆に言うとエンドユーザーでもこのDebugViewを使えばデバッグログをキャプチャすることができるので、リリースビルドで重要な情報をログ出力するのはご法度です。普通はデバッグビルドでのみ出力されるようにします。

*2:厳密に言うと「文字化け」というよりは、Unicode文字からCP932 (Shift_JIS) 文字へのマッピングが不可能であるために、代替文字に自動変換されています。

*3:昔はATLとMFCは別々に実装・メンテナンスされていたのですが、VC7で統合・整理されました。以後、MFCアプリからはATLの機能がすべて使えるようになっています。さらにVC2013以降はDLL版のATLが廃止され、スタティックリンク版のみになりました。Microsoft C/C++ 2003 - 2015 の変更履歴 | Microsoft Learn

絶対値関数absとオーバーフロー

C/C++において、符号付き整数の絶対値を求める関数abs(), labs(), llabs()および整数型のstd::abs()関数オーバーロードには罠があり、「整数オーバーフロー」が考慮されていません。
これは、JavaC#のような近代的な言語と違って、C/C++の整数内部表現が2の補数系であるとは限らない、ということも関係しています。

C言語では、2の補数系において、整数型を受け取り整数型を返す絶対値関数にINT_MINLONG_MINなどが渡されたときには、各絶対値がそれぞれの型intlongなどで表現不可能なため、未定義動作 (undefined behavior) を引き起こします。要するにそれらの値を渡した場合は実際に何が起きるか分からないので絶対に渡すな、事前にチェックしろということです。

同様にC++では、2の補数系において、整数型を受け取り整数型を返すstd::abs()関数オーバーロードINT_MINLONG_MINなどが渡されたときには、未定義動作 (undefined behavior) を引き起こします。
なお、C++03以前では、<cmath>には浮動小数点数型を受け取るstd::abs()オーバーロードのみが存在しますが、C++11以降では整数型を受け取るオーバーロードも存在します。
また、C++14以前では、<cstdlib>には整数型を受け取るstd::abs()オーバーロードのみが存在しますが、C++17以降では浮動小数点数型を受け取るオーバーロードも存在します。

C# (.NET) のMath.Abs()メソッドではOverflowException例外がスローされます。

JavaMath.abs()メソッドでは入力と同じ値を返します。

C/C++では、関数呼び出し側が入力の範囲を保証するべき(オーバーヘッドを最小化するために関数内部での入力チェックを省く)、という設計思想の関数が多いんですが、いちいちアプリケーションコードで入力チェックするラッパーを書くのもダルいので、C/C++でもsafe_abs()みたいな関数を標準化して欲しいですね。
成否をbool型またはerrno_t型の戻り値で返して、成功すれば絶対値を引数T& or T*で返すとか。あるいはC++17で追加されたstd::optionalを使って戻り値として返す設計でも良いと思います。
整数オーバーフローが発生すると、以後の計算結果が不正になる可能性が高いので、いっそのこと.NETのように例外を投げてしまい、強制的に処理を中断するというのもアリだと思います。以下ではJavaでラッパーメソッドsafeAbs()を実装して例外処理する手法が紹介されています。

ちなみにC++20からは、ついに符号付き整数型が2の補数表現であることが規定されました。現実がまったく見えていない、旧世紀に生きているC++標準化委員会の干からびた老人どもがようやく重い腰を上げた、といったところですが、JavaC#に対して実に20年以上遅れていましたね。

Cの最新規格C17 (C18) では今のところ規定されていませんが、次期標準規格 C2x では2の補数表現のみが許容されるようになることが N2412 によって提案されているそうです。

今後、C言語でも2の補数表現が規定されるようになったら、少なくともJavaのように、絶対値関数が返す値を規定して未定義動作を排除するか、あるいは返す値を規定した標準ラッパーを定義して欲しいところです。

余談ですが、C/C++Unicodeサポートもかなり遅れています。JavaC#はともに20年以上前の登場当初からUTF-16を標準サポートしていたのに、C/C++では未だにchar16_t文字列を標準出力することすらままなりません。時代遅れとかいうレベルを超越しています。現実が何ひとつ見えていない。それだけでなく、UTF-8リテラルの型も結局charから後出しのchar8_tに変更されるなど、最近は仕様策定ミスを撤回するために互換性を破壊する仕様変更が頻繁に入るようになりました。ちょっと考えれば分かりそうなことなのに、標準化委員会は後先考えることもできない無能集団以外の何物でもありません。自称・C++のエキスパートらしいですが、現役のプログラマーやエンジニアですらない頭でっかちなダボハゼ連中です。

※2023-02-19追記:
C++23でようやくUnicode文字列の標準出力をサポートするstd::vprint_unicode()が追加されるようです。すさまじい周回遅れ。

C/C++よりも安全なシステムプログラミング言語としてRustが台頭してきたので、C/C++の標準化委員会は焦っているんでしょうね。しかしC/C++には依然として未定義動作・未規定動作や処理系依存動作が多数残っており、安全性の問題は解決されていないので、このままだと確実にC/C++は時代遅れの化石となることが目に見えています。

日本語のIT関係記事は提灯であふれている

日本(日本語)のIT関係記事は、意図的に事実を捻じ曲げ、読者に誤解をさせるような偏向的な眉唾記事であふれています。すべてステークホルダーへの忖度です。

個別のテクノロジーに精通している人間でなくとも、海外ベンダーが英語で公式に発信している原文(一次情報)に触れれば、ライター連中が日本語で書いていることが嘘八百であることにすぐ気づくのですが、日本人は英語ができないため、英語の一次情報にほとんど触れようとしない*1*2ことから、簡単にだませます。

pcnomori.blog.fc2.com

最近見た記事では、Apple M1に関する笠原一輝の記事(↓)が一番ひどかったですね。低俗な日本のメディアによくある、典型的な釣りタイトルです。連中にとっては情報の正確性よりもPV数のほうが大事なんでしょう。
特にコイツのベンチマークGPUとグラフィックスAPIを知っている人間からすれば噴飯ものです。Windows+OpenGLMac+Metalで比較するとか、意図的にMac側に有利な条件でテストしておいて、このタイトルですからね。そもそも中学生でも知っているような対照実験の原則を完全に無視しています。
Appleから金をつかまされてクソみたいな提灯記事を書いているのか、それともこの笠原一輝がただのApple信者なのかは知りませんが、こんなひどいバイアス記事を書いて金をかせいでいるクズが日本にいて、さらにそれを盲目的に信じる情報弱者が少なからずいるのかと思うと情けなくなります。

pc.watch.impress.co.jp

*1:実際はライティングはともかくリーディングに関しては高校卒業レベルでも大抵なんとかなるはずで、読めるはずなのに読もうとしないだけです。要するにただの英語アレルギーですね。

*2:Wikipediaではなぜか「二次情報源 > 一次情報源」というおかしな主張がまかり通っていますが、二次情報が不確かで間違っているということはよくあります。特に日本語への翻訳は誤訳だらけなのに、母語で書かれた二次情報のほうが正確であると盲目的に信奉する人間が多いようです。