Table of Contents
コンパイラによる安全性と正確性のメリットを活用
新しいコンパイラは、主に以下の2つのことを行います。
- 最適化が大幅に改善され、可能な限り最適化を行う(パフォーマンスに最適)
- 予期せぬことが起こった場合、ユーザーへ通知する
特に2.の項目が重要です。コンパイラは可能な限り最適化を行いますが、コードが正しくない場合、未定義の動作に依存することがよくあります。このため、以前は「動作していた」コードが動作しなくなる可能性があるため、非常に心配されるかもしれません。しかし、その心配には及びません。2.が有用なのは、もしそのような状況が発生した場合、コンパイラが警告してくれる点です。
この例については、以前 数ヶ月前に別のブログで取り上げました。 主にコードが初期化されていない変数を使用していたことが原因でした。つまり、その変数が配置されているメモリ内容に依存し、コードが意図せずランダムに動作していたことになります。
ヒント:初期化されていない変数は、動作しているように見えて、実際には動作していないコードの典型的な例です。初期化されていないbool値は、その変数が配置されているメモリ内の内容に基づいて、単に「true」または「false」となります。プログラム、テストデータ、特定のコンパイラおよびRTLのバージョンによっては、その値が常に「true」または「false」として評価される可能性が高いです。長年、99%の確率でそのようになっていたケースが多いため、あまり気づかない事例でした。
新しいツールチェーンでは、変数の値に基づいて最適化を行う方針ですが、初期化されていない変数の値に依存することは適切ではないと判断しております。そのため、不正なコードパスに対してはトラップ(クラッシュ)を挿入します。つまり、そのトラップを回避できれば、最適化が正しく行われていることが保証されます。すべて理にかなっていますが、予期せぬことが起こる可能性もあります。例えば、この事象はまさに「以前は動作していたコードが、新しいコンパイラで最適化された同じコードでは動作しなくなった」という状況です。
ここで重要な点は、もしそのような事態が起こった場合は、新しいコンパイラが警告メッセージを通知してくれるため、開発者はそのメッセージから判断して問題箇所を特定し、修正することができます。
- 1,000 回に 1 回しか発生しないバグはありますか?
- デバッグモードでコンパイルすると直るバグはありますか?
- ユーザーから報告されているのに、開発者側で再現しないバグはありますか?
もしご自身の既存のC++コードで再現性が低く不可思議な問題に遭遇した経験がある場合は、新しいツールチェーンを使用してみてください。新しいツールチェーンのコンパイラによって長年、ご自身のコード内で潜在的なだった問題点をキャッチアップし、顕在化する手がかりになるかもしれません。
例えば、コンパイラの警告メッセージは、以下のオプションで有効化できます。
- -Wall すべての警告付きでコンパイルする ( 最低限このオプションの有効を推奨)
- -Wextra さらに多くの警告を表示するには-Wallと併用 (ただし、このオプションは一部は誤検知である可能性があり)
情報: 実際に新しいツールチェーンが原因で問題も見つかりました。新しいコンパイラは、他のコンパイラ(他の主流のC++ Windowsコンパイラを含む)では検出されない問題を検出します。エンバカデロのコードベースで警告された問題箇所は全て修正しました。この警告のおかげで、今まで潜在的な問題も解決できたため、この警告は貴重であり、非常に効果的だったことが実証されています。
もちろん、最適化を無効にしたり、最適化の一部を無効にするオプション設定を行うこともできます。しかし、最適化オプションを使用する方がはるかに効果的です。もしご自身のコードに何か問題があれば、コンパイラが直接かつ明確に通知してくれますので、是非ご活用してください。
コンパイラのセーフティオプションを確認
続いて、コードをより安全に保つためのコンパイラ設定に関するヒントをご紹介いたします。
- コンパイラがすでにNullPointerを逆参照したと判断した場合、一部のNullPointerのチェックが最適化されないものがあることをご存知でしょうか?
- -fno-delete-null-pointer-checks
- スイッチケースの見落としを検出する警告があることをご存知でしょうか?
- -Wimplicit-fallthrough
- クラシックコンパイラでは、このような警告は行われませんでした。
- フォーマット関数(例えば、 printf) の使用状況を確認するためのオプションが複数用意されているのをご存知でしょうか?
これらの情報はすべて、以下の2つのサイトから確認できます。
- Compiler Options Hardening Guide for C and C++ by the Open Source Security Foundation (OpenSSF) Best Practices Working Group
- Leveraging Your Toolchain to Improve Security by Phillip Johnston
どちらのサイトの情報も複数のコンパイラと標準ライブラリを網羅しています。Clangのバージョン<= 15とLLVMのSTLでエンバカデロが使用しているlibc++のバージョン<= 15に適用されるフラグを確認してください。(Clangバージョン16-18向けのフラグはまだ適用されていませんが、将来適用されるようになります。) その中には、ご自身のコードを改善する優れたフラグがいくつか用意されており、新しいC++Builder 12.2とWin64 モダン C++ツールチェーンで使用できます。
32-bitコードから64-bitコードへ移行する際のヒント
最後に32-bitコードから64-bitコードへの移行するためのヒントや役に立つ情報をご紹介いたします。
新しいWin64モダンツールチェーンは本当に優れており、まだお客様の全てが64-bitへ移行しているわけではありませんが、古いClang 32-bitコンパイラ(bcc32c)やクラシックコンパイラ(bcc32)からの移行を検討している方もいらっしゃるかもしれません。実際に新しいWin64モダンツールチェーンへ移行するということは、以下のような2つの移行を同時に実行することになります。
- 32-bitから64-bitへの移行
- 既存のコンパイラから別のコンパイラへの移行
エンバカデロでは、コンパイラへの移行に関して互換性を最大限に確保し、高い互換性と優れた機能を確保するために最善を尽くしました。そのため、おそらく主に取り組むべきことは、32-bitコードを64-bitコードとしてコンパイルできるか、そして正常にアプリケーションが動作するのかといった検証でしょう。そして、そこで発生する問題の大部分は、ポインタの切り捨て、本来の型よりも狭い型へのキャスト(データサイズの切り捨て)、ポインタ演算のミスなどです。
- -Wpointer-sign
- -Wpointer-to-int-cast
- -Wpointer-type-mismatch
- -Wsizeof-pointer-memaccess
- -Wvoid-pointer-to-int-cast
- …その他多数
コンパイラには、まさにこれらの問題を検出する様々な警告オプションが用意されています(サイズの切り捨て以外にも、はるかに多くの問題を検出可能)。そのため、32-bitから64-bitへの移行に伴う問題を検出するには、上記のような警告オプションを有効にすることができます。この診断リファレンス(「ポインタ」で検索してみてください)を一読することで必要な情報を見つけることができますが、最も簡単な方法は、-Wall(すべての警告)または-Wextra(すべてよりも多くの警告が表示されますが、一部は誤検知である可能性があるので要注意)を指定して実行することです。
コンパイラは開発者へ必要な情報を伝え、サポートしてくれます。本当に役立ちますので、32-bitから64-bitコードを移植している場合は、警告をオンにしてください。
ウェビナーについて
本ブログの内容は、エンバカデロのC++のブロダクトマネージャである「David Millington」が、ウェビナー内で「Tip #4, Take Advantage of Security and Correctness」、「Tip #4 1/2: Check out compiler safety options」、「Tip #4 3/4: Help When Moving 32 → 64」で解説しています。このウェビナーはC++Builder / RAD Studio 12.2の新しいWin64 モダンツールチェインを活用するためのヒントや非常に有益な情報を紹介しています。もしウェビナー(英語)をご覧になりたい場合は、以下をご参照ください。
「Tip #4, Take Advantage of Security and Correctness」の節は、下記の動画の「1:06:22〜」です。