3

私は、C++ と ActiveX で書かれた複雑なレガシ システムであるプロジェクトに配属されており、そのプロジェクトは 10 年ほど前のものです。

セットアップは Microsoft Visual Studio 2008 です。

現在、システムに問題はありませんが、レガシー システムのセキュリティ レビューの一環として、自動化されたセキュリティ コード スキャン ツールは、セキュリティの脆弱性のため、realloc のインスタンスを悪い慣行の問題としてマークしました。

これは、realloc 関数によって機密情報のコピーがメモリ内に取り残され、上書きできない可能性があるためです。このツールは、realloc を malloc、memcpy、および free に置き換えることを推奨しています。

現在、realloc 関数は用途が広く、ソース バッファーが null の場合にメモリを割り当てます。また、バッファのサイズが 0 の場合はメモリを解放します。これらの両方のシナリオを確認できました。出典: MDSN ライブラリ 2001

realloc は、再割り当てされた (場合によっては移動された) メモリ ブロックへの void ポインターを返します。サイズがゼロで buffer 引数が NULL でない場合、またはブロックを指定されたサイズに拡張するのに十分なメモリがない場合、戻り値は NULL です。最初のケースでは、元のブロックが解放されます。2 番目では、元のブロックは変更されません。戻り値は、あらゆるタイプのオブジェクトのストレージに適切に配置されることが保証されているストレージ スペースを指します。void 以外の型へのポインターを取得するには、戻り値で型キャストを使用します。

そのため、malloc、memcpy、および free を使用する私の代替関数は、これらのケースに対応する必要があります。

realloc を使用して内部バッファーのサイズを動的に変更および縮小する元のコード スニペット (配列の実装) を以下に再現しました。

まずクラス定義:

template <class ITEM>
class CArray 
{
// Data members:
protected:
ITEM                *pList;
int                 iAllocUnit;
int                 iAlloc;
int                 iCount;

public:
CArray() : iAllocUnit(30), iAlloc(0), iCount(0), pList(NULL)
{
}

virtual ~CArray()
{
    Clear(); //Invokes SetCount(0) which destructs objects and then calls ReAlloc
} 

既存の ReAlloc メソッド:

void ReAllocOld()
{
    int iOldAlloc = iAlloc;

    // work out new size
    if (iCount == 0)
        iAlloc = 0;
    else
        iAlloc = ((int)((float)iCount / (float)iAllocUnit) + 1) * iAllocUnit;

    // reallocate
    if (iOldAlloc != iAlloc)
    {
        pList = (ITEM *)realloc(pList, sizeof(ITEM) * iAlloc);
    }
}

以下は、これらを malloc、memcpy、および free に置き換えた私の実装です。

void ReAllocNew()
{
    int iOldAlloc = iAlloc;

    // work out new size
    if (iCount == 0)
        iAlloc = 0;
    else
        iAlloc = ((int)((float)iCount / (float)iAllocUnit) + 1) * iAllocUnit;

    // reallocate
    if (iOldAlloc != iAlloc)
    {

        size_t iAllocSize = sizeof(ITEM) * iAlloc;

        if(iAllocSize == 0)
        {
            free(pList); /* Free original buffer and return */
        }
        else
        {
            ITEM *tempList = (ITEM *) malloc(iAllocSize); /* Allocate temporary buffer */

            if (tempList == NULL) /* Memory allocation failed, throw error */
            {
                free(pList);
                ATLTRACE(_T("(CArray: Memory could not allocated. malloc failed.) "));
                throw CAtlException(E_OUTOFMEMORY);
            }

            if(pList == NULL) /* This is the first request to allocate memory to pList */
            {
                pList = tempList; /* assign newly allocated buffer to pList and return */
            } 
            else 
            {
                size_t iOldAllocSize = sizeof(ITEM) * iOldAlloc;        /* Allocation size before this request */
                size_t iMemCpySize = min(iOldAllocSize, iAllocSize);    /* Allocation size for current request */

                if(iMemCpySize > 0)
                {
                    /* MemCpy only upto the smaller of the sizes, since this could be request to shrink or grow */
                    /* If this is a request to grow, copying iAllocSize will result in an access violation */
                    /* If this is a request to shrink, copying iOldAllocSize will result in an access violation */
                    memcpy(tempList, pList, iMemCpySize); /* MemCpy returns tempList as return value, thus can be omitted */
                    free(pList); /* Free old buffer */
                    pList = tempList; /* Assign newly allocated buffer and return */
                }
            }

        }
    }
}

ノート:

  1. オブジェクトは、古いコードと新しいコードの両方で正しく構築および破棄されます。

  2. メモリ リークは検出されませんでした (CRT Debug ヒープ関数に組み込まれている Visual Studio によって報告されています: http://msdn.microsoft.com/en-us/library/e5ewb1h3(v=vs.90).aspx )

  3. 次のことを行う小さなテスト ハーネス (コンソール アプリ) を作成しました。

    を。2 つの整数と STL 文字列を含むクラスの 500000 インスタンスを追加します。

    追加された整数は、次のようなカウンターとその文字列表現を実行しています。

    for(int i = 0; i < cItemsToAdd; i++)
    {
        ostringstream str;
        str << "x=" << 1+i << "\ty=" << cItemsToAdd-i << endl;
        TestArray value(1+i, cItemsToAdd-i, str.str());
        array.Append(&value);
    }
    

    b. さまざまな長さの 86526 行を含む大きなログ ファイルを開き、次の配列のインスタンスに追加します: CString の CArray と文字列の CArray。

既存の方法 (ベースライン) と変更した方法でテスト ハーネスを実行しました。デバッグ ビルドとリリース ビルドの両方で実行しました。

結果は次のとおりです。

テスト 1: デバッグ ビルド -> int、int、string、100000 インスタンスのクラスを追加:

元の実装: 5 秒、修正された実装: 12 秒

テスト 2: デバッグ ビルド -> int、int、string、500000 インスタンスのクラスを追加:

元の実装: 71 秒、修正された実装: 332 秒

テスト 3: リリース ビルド -> int、int、string、100000 インスタンスのクラスを追加:

元の実装: 2 秒、修正された実装: 7 秒

テスト 4: リリース ビルド -> int、int、string、500000 インスタンスのクラスを追加:

元の実装: 54 秒、修正された実装: 183 秒

大きなログ ファイルを CString オブジェクトの CArray に読み込む:

テスト 5: デバッグ ビルド -> 86527 行の CString の CArray を含む大きなログ ファイルを読み取る

元の実装: 5 秒、修正された実装: 5 秒

テスト 6: リリース ビルド -> 86527 行の大きなログ ファイルを読み取る CString の CArray

元の実装: 5 秒、修正された実装: 5 秒

大きなログ ファイルを文字列オブジェクトの CArray に読み込む:

テスト 7: デバッグ ビルド -> 文字列の 86527 行の CArray を含む大きなログ ファイルを読み取る

元の実装: 12 秒、修正された実装: 16 秒

テスト 8: リリース ビルド -> 86527 行の大きなログ ファイルを読み取る文字列の CArray

元の実装: 9 秒、修正された実装: 13 秒

質問:

  1. 上記のテストからわかるように、realloc は memalloc、memcpy、および free と比較して一貫して高速です。場合によっては (Test-2 など)、なんと 367% 高速になります。同様に、テスト 4 の場合は 234% です。では、realloc の実装に匹敵するこれらの数値を下げるにはどうすればよいでしょうか?

  2. 私のバージョンをより効率的にすることはできますか?

仮定:

  1. C++ の new と delete は使用できないことに注意してください。malloc と free のみを使用する必要があります。また、他の方法を変更することもできず (既存の機能であるため)、影響は非常に大きくなります。したがって、できる限り realloc の最適な実装を得るために、私の手は縛られています。

  2. 変更した実装が機能的に正しいことを確認しました。

PS: これは私の最初の SO 投稿です。できるだけ詳しく説明するように努めました。投稿に関する提案も大歓迎です。

4

3 に答える 3

2

例えば ​​realloc の実装を見ると

http://www.scs.stanford.edu/histar/src/pkg/ulibc/libc/stdlib/malloc/realloc.c

実装と既存の実装の違いは、低レベルの呼び出しを使用してまったく新しいブロックを作成するのではなく、メモリ ヒープ ブロックを拡張することです。これはおそらく、速度の違いの一部を説明しています。

パフォーマンスの低下は避けられないように見えるため、再割り当てを行うたびに、メモリの memset の影響も考慮する必要があると思います。

通常の malloc/calloc/free についても同じことが言えます。つまり、すべての reallocs/malloc/callocs だけでなく、それらの関数を内部で使用するランタイムまたはサード パーティの関数を見つけて、メモリに何も保持されていないことを確認する必要があることを意味します。別の方法として、独自のヒープを作成することもできます。通常のものと交換して清潔に保ちます。

于 2013-11-07T11:51:46.167 に答える