下記のような関数があるとします。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | - | ! - | | | - - ! | ! | | ! |
|
処理内容は下記の通りです。
std::to_wstring
関数で文字列に変換する。CreateMutexW
関数を呼び出し、ミューテクスを作成。GetLastError
関数の戻り値が ERROR_ALREADY_EXISTS
ならば既に作成済みのミューテクスなので解放して nullptr
を返す。特に問題はなさそうに見えますが、ある特定の状況だとこの関数は意図通りに動きません。
その状況とは、グローバルの delete
演算子がオーバロードされ、その中で GetLastError
関数の内部値が上書きされた場合です。
上記の例では直接 SetLastError
関数を呼んでいますが、実際には何らかの Win32 API が呼ばれているものと思ってください。
先のコードの、 CreateMutexW
関数を呼んでいる文は次の通りです。
8 |
|
std::to_wstring(id)
で作成した std::wstring
は特に変数等に束縛されていないため、この文の処理が完了した時点でデストラクタが呼び出されます。
std::wstring
のデストラクタは、内部に保持しているバッファを delete
演算子によって解放します。
よって、 GetLastError
関数を呼び出すよりも前に operator delete
が呼び出され、内部値が上書きされてしまいます。
GetLastError
関数を利用するコードと delete
演算子のオーバロードをどちらも自身で書いたならば気付けるかもしれませんが、下記のような場合は非常に気付きにくいです。
GetLastError
関数を利用するコードが入っている場合。delete
演算子がオーバロードされている場合。グローバルの new
, delete
演算子のオーバロードはリンクしているライブラリにまで波及するのがとても厄介です。
両者の組み合わせが一番最悪で、どちらかのソースコードを修正可能でなければどうしようもなくなってしまいます。
私は、自身で作ったライブラリと Unreal Engine 4 (ゲームエンジン)の組み合わせでこの問題にハマり、原因判明までに丸1日を要しました…。
個人的には「グローバルの new
, delete
演算子をオーバロードするのはやめろ」と言いたいのですが、外製のフレームワークにやられていたらどうしようもないですし、 GetLastError
関数の仕組みが危険なのもまた事実なので、注意してコーディングするしかないですかね。
なお、今回のコード例であれば、 std::to_wstring(id)
の結果を一旦変数に受け取ることで問題を回避できます。
より安全にするなら、最初に OpenMutexW
関数によって既存のミューテクスが存在するか確認するようにすれば、 GetLastError
関数を使わずとも同等の処理が可能です。