EN JP CN

CL.FFM.ASSIGN

CL.FFM.ASSIGN

演算子の欠落による解放されたメモリの解放

クラスレベルのチェッカーは有効な C++ のための Scott Meyer 規則クラス構築に基づいて推奨を通知します。

CL.FFM.ASSIGN は Scott Meyer の 11 項「動的割り当て済みメモリを持つクラス用のコピーコンストラクタと代入演算子の宣言」に基づくものです。このチェッカーは、動的割り当て済みデータメンバーを含むが、代入演算子が定義されていないクラスを検出します。

代入演算子が実装されていない場合、C++ コンパイラは必要に応じて代入演算子を自動生成します。しかし、そのコンパイラが提供する実装は常に形式的なものにすぎません。

データメンバーを管理するようにコピーが明示的にコード化されていない場合、オブジェクトをコピーすると動的に割り当てられる単一のデータメンバーを参照する 2 つのオブジェクトができます。ポインターが値によって単純にコピーされる形式的なコピー操作では、オブジェクトのペアが生成され、その両方が同じ基底のヒープメモリをポイントします。そのヒープメモリ上で実行するどの操作も、その操作への参照を維持する両方のオブジェクトに影響し、すべてのタイプのプログラムにおいて予期しない結果をもたらす可能性があります。

この特定のチェッカーが参照する状況では、すでに解放されたメモリを解放する可能性があり、これは共通の基底の割り当てを共有している、そのような 2 つのオブジェクトが範囲外になる場合に発生します。

脆弱性とリスク

この状況では、通常は、範囲外になる最初のオブジェクトが、現在他のオブジェクトと共有されているバッファを含めて、関連するすべてのヒープメモリを解放します。2 番目のオブジェクトが範囲外になると、独自のメモリリソースと見なしたメモリを解放しようとする結果、すでに解放しているメモリにアクセスすることになり、最悪の場合、ヒープを破壊する可能性があります。

軽減と防止

この問題に対処するには、動的に割り当てられたデータメンバーを含むクラスに対しては常に代入演算子を明示的に実装し、そしてこの代入演算子がこれらのデータメンバーを実質的にコピーするようにします。

脆弱コード例

     
1    #include <iostream>
2    using namespace std;
3    class C{
4      char *data;
5      C(const C&){}
6    public:
7      C(){  data = new char[10]; }
8      ~C() {
9        cout << "Calling delete for " << (void *)data << endl;
10        delete[] data;
11      }
12    };
13    int main(){
14      C c1;
15      C c2;
16      c1 = c2;
17      return 1;
18    }


Output:

Calling delete for 0x602030
Calling delete for 0x602030

この例では、クラス 'C' は 'C::data' に対して動的メモリ割り当てを使用していますが、演算子 = を定義していません。その結果、関数 'main()' の 16 行目が実行されると、'c1.data' と'c2.data' は両方とも同じ値になり、破壊されるときにそれぞれが演算子 'delete[ ]' を呼び出し、同じポインターが 2 回削除されることになります。この場合は、CL.FFM.ASSIGN は演算子 = の実装による、解放されたメモリがもう一度解放される典型的な例を検出しました。

修正コード例

この問題を修正するには、演算子 = を定義することが必要です。状況により、演算子 = の別の実装も使用できます。

  • 新しいメモリの割り当てが必要な場合は、演算子 = の実装では、自身をコピーしない、古いメモリを解放する、新しいメモリを割り当てる、データをコピーする、をチェックする必要があります。
1    class C{
// ...
2      C& operator=(const C& src){
3        if (&src == this) return *this;
4        delete[] data;
5        data = new char[10];
6        memcpy(data, src.data, 10);
7        return *this;
8      }
// ...
9   };
  • 前の実装では、破壊されたデータとその割り当て済みデータが同じサイズであるため、不必要なヒープメモリ操作が実行されます。そのような場合、古いメモリは再利用できます。
1    class C{
// ...
2      C& operator=(const C& src){
3        if (&src == this) return *this;
4        memcpy(data, src.data, 10);
5        return *this;
6      }
// ...
8    };
  • クラスインスタンスのコピーを想定しない場合、演算子 = を private として定義する必要があります。この場合、コピーしようとするとコンパイラはエラーを生成します。
1    class C{
// ...
2    private:
3      C& operator=(const C&){ return *this;}
// ...
4    };

拡張機能

このチェッカーは、Klocwork knowledge base (ナレッジベース) を利用して拡張できます。詳細については、C/C++ 解析のチューニングを参照してください。