ODAメモリ割り当ての概要
ODAは、その技術を進歩させ、改善する方法を常に模索しています。その一例が、メモリ割り当ての処理方法です。
以前、ODAソフトウェアは、TD_Allocモジュールで定義され、このモジュールからすべてのODAプロジェクトにエクスポートされたオーバーライドされたnew/deleteグローバル演算子に基づいてメモリを割り当てていました。その理由は単純で、メモリリークを捕捉し、ODAメンバー向けにカスタムメモリ管理を記述する機能を提供するためでした。しかし、この方法は以下の理由により不適切でした。
-
C++標準への非準拠。C++17のドラフト版には次のように記載されています。
3.7.4.2項、65ページ
「ライブラリは、グローバルな割り当ておよび解放関数のデフォルト定義を提供します。一部のグローバルな割り当ておよび解放関数は置き換え可能です (18.6.1)。C++プログラムは、置き換え可能な割り当てまたは解放関数の定義を最大1つ提供しなければなりません。そのような関数定義は、ライブラリで提供されるデフォルトバージョンを置き換えます (17.6.4.6)。」
17.6.4.6項、460ページ
「C++プログラムは、ヘッダーで宣言された12の動的メモリ割り当て関数シグネチャのいずれかの定義を提供できます (3.7.4, 18.6): [...] プログラムの定義は、実装によって提供されるデフォルトバージョンではなく使用されます (18.6)。」
標準は「プログラム」を次のように定義しています。
「プログラムは、1つ以上の翻訳単位(第2章)がリンクされたもので構成されます。」
これを考慮すると、各DLLは「プログラム」であるため、各DLLは独自の内部バージョンのメモリ割り当て演算子を持つべきです。
- ODAメンバーは、「ランダムなクラッシュ、割り当てと削除の不一致」などの問題や質問を報告しました。例えば、当社のフォーラム(ログインが必要です)をご覧ください。さらに、多くのメンバーがサードパーティのメモリリーク検出器をODAモジュールで使用する際に問題を抱えていました。
- 同じグローバルな割り当てまたは解放演算子の複数のバージョンが定義されている状態でODAライブラリをロードすることは未定義の動作であり、他の問題を引き起こす可能性があります。
- 内部的には、独自のメモリリーク検出器を使用している際に定期的に問題が発生していました。割り当て演算子がグローバルにオーバーライドされていたため、ODAプロジェクトには強力な依存関係のリンク順序要件がありました。標準の割り当て演算子のオーバーライドを管理するために、TD_Allocはモジュール依存関係内の他のどのライブラリよりも前に来るべきでした。リンク順序をチェックするための特別なスクリプトがOdaProjectGeneratorにさえありました。また、新しくサイズ指定された解放演算子(C++14標準)を追加する際に問題がありました。これらの演算子に単に「export」呼び出し規約を追加しても機能しなかったため、以前の回避策として.defファイルを使用する必要がありました。
メモリ割り当てのリファクタリング
メモリ割り当てのリファクタリング中には、いくつかの目標がありました(前の段落で説明した問題の解消を除く):
- 既存の機能を維持する。
- ODA開発者にとって変更を透過的にする:既存のモジュールを書き直すための長い時間を要する要件(各モジュールに個別に割り当て演算子を追加するなど)を避ける。
- ODA開発者およびODAメンバーが、当社のアロケーターを使用してさらなるモジュール/拡張機能を簡単に作成できるようにする。
そこで、以下の変更が行われました。
- 新しいアロケーション演算子はOdAllocOp.hファイルで定義され、oda_txおよびoda_txvモジュール用にマクロODRX_DEFINE_DYNAMIC_MODULEを介して追加されました。そのため、ODA開発者およびODAメンバーがODAベースのアプリケーションの新しい拡張機能を作成する際、これらの演算子をまったく覚える必要はありません(ただし、odrxAlloc/odrxFree関数を提供するTD_Allocモジュールは、引き続きモジュールにリンクする必要があります)。
- DLLモジュールのサポートのために、新しいモジュールタイプoda_tx_componentが追加されました。これにはODRX_DEFINE_DYNAMIC_MODULEマクロも含まれています(主に内部使用のため)。
- 他のDLLモジュール(oda_components)の場合、演算子はOdAllocOp.cppファイルに追加されました。このファイルにはOdAllocOp.hと同じものが含まれています。生成段階で、CMakeはプロジェクトを解析し、TD_Allocを依存関係に持つコンポーネントにこのファイルを追加します。
- ODAライブラリモジュールは、TD_Allocから独立できるようになりました(odrxAlloc/odrxFreeがそこから直接呼び出されない場合)。
- 上記のすべては動的構成のために行われました。しかし、実行可能モジュール(oda_executables)の場合、OdAllocOp.cppはCMakeによって動的構成と静的構成の両方に追加されるようになりました。
現在の仕組み
これは簡単です。C++標準に従い、new/delete演算子は各ODAモジュールに対して独立して定義されました。TD_Allocは、メモリリークおよびメモリ管理の目的でodrxAlloc/odrxFree関数を引き続き提供しますが、グローバルなnew/delete演算子を上書きすることはなくなりました。
拡張モジュールを作成するために、以前に必要とされなかったものは何も必要ありません。また、TD_Allocがモジュールの依存関係に追加されていない場合、ビルドエラーが発生します。
他のモジュールの場合、TD_Allocをモジュールの依存関係に追加する必要があり、ODAProjectGeneratorを使用しない場合は、Extensions/allocからOdAllocOp.cppをプロジェクトに追加する必要があります(使用する場合は、ファイルは自動的に追加されます)。