0

これは、現在実行中のプログラムの統計を出力する印刷スレッドです

void StatThread::PrintStat(){
clock_t now = 0;
UINT64 oneMega = 1<<20;
const char* CUnique = 0;;
const char* CInserted = 0;;
while((BytesInserted<=fileSize.QuadPart)&&flag){
    Sleep(1000);
    now = clock();
    CUnique = FormatNumber(nUnique);
    CInserted = FormatNumber(nInserted);
    printf("[ %.2f%%] %u / %u dup %.2f%% @ %.2fM/s %.2fMB/s %3.2f%% %uMB\n",
        (double)BytesInserted*100/(fileSize.QuadPart),
        nUnique,nInserted,(nInserted-nUnique)*100/(double)nInserted,
        ((double)nInserted/1000000)/((now - start)/(double)CLOCKS_PER_SEC),
        ((double)BytesInserted/oneMega)/((now - start)/(double)CLOCKS_PER_SEC),
        cpu.GetCpuUtilization(NULL),cpu.GetProcessRAMUsage (true));
    if(BytesInserted==fileSize.QuadPart)
        flag=false;
}
delete[] CUnique;    //would have worked with memory leak if commented out
delete[] CInserted;  // crash at here! heap corruption 
}

これは、char 配列へのポインタを返す FormatNumber です。

const char* StatThread::FormatNumber(const UINT64& number) const{
char* result = new char[100];
result[0]='\0';
_i64toa_s(number,result,100,10);
DWORD nDigits = ceil(log10((double)number));
result[nDigits] = '\0';
if(nDigits>3){
    DWORD nComma=0;
    if(nDigits%3==0)
        nComma = (nDigits/3) -1;
    else
        nComma = nDigits/3;
    char* newResult = new char[nComma+nDigits+1];
    newResult[nComma+nDigits]='\0';
    for(DWORD i=1;i<=nComma+1;i++){
        memcpy(newResult+strlen(newResult)-i*3-(i-1),result+strlen(result)-i*3,3);
        if(i!=nComma+1){
            *(newResult+strlen(newResult)-4*i) = ',';   
        }
    }
    delete[] result; 
    return newResult;
}
return result;
}

本当に奇妙なのは、ヒープの破損が原因でリリース モードでのみクラッシュしましたが、デバッグ モードではスムーズに実行されたことです。私はすでにどこでもチェックしましたが、明らかなメモリリークは見つかりませんでした.Memory Leak Detectorでさえそう言っています.

Visual Leak Detector Version 2.2.3 installed.
The thread 0x958 has exited with code 0 (0x0).
No memory leaks detected.
Visual Leak Detector is now exiting.
The program '[5232] Caching.exe' has exited with code 0 (0x0).

ただし、リリース モードで実行すると、プログラムが動作を停止するというエラーがスローされ、デバッグをクリックすると、ヒープ破損の原因となった行が示されました。

The thread 0xe4c has exited with code 0 (0x0).
Unhandled exception at 0x00000000770E6AE2 (ntdll.dll) in Caching.exe:          0xC0000374: A heap has been corrupted (parameters: 0x000000007715D430).

この行をコメントアウトすると、問題なく動作しましたが、Memory Leak Detector はメモリ リークについて不平を言っていました。メモリ リークがないときにヒープの破損を引き起こす方法がわかりません (少なくとも、リーク ディテクターはそう言っています)。助けてください、よろしくお願いします。

編集:ヒープの破損が修正されました。これは、最後の反復で、残りの代わりに 3 バイトを前にコピーしたためです。助けてくれてありがとう!

const char* StatThread::FormatNumber(const UINT64& number) const{
char* result = new char[100];
result[0]='\0';
_ui64toa_s(number,result,100,10);
DWORD nDigits = (DWORD)ceil(log10((double)number));
if(number%10==0){
    nDigits++;
}
result[nDigits] = '\0';
if(nDigits>3){
    DWORD nComma=0;
    if(nDigits%3==0)
        nComma = (nDigits/3) -1;
    else
        nComma = nDigits/3;
    char* newResult = new char[nComma+nDigits+1];
    DWORD lenNewResult = nComma+nDigits;
    DWORD lenResult = nDigits;
    for(DWORD i=1;i<=nComma+1;i++){
        if(i!=nComma+1){
            memcpy(newResult+lenNewResult-4*i+1,result+lenResult-3*i,3);
            *(newResult+lenNewResult-4*i) = ',';    
        }
        else{
            memcpy(newResult,result,lenNewResult-4*(i-1));
        }
    }
    newResult[nComma+nDigits] = '\0';
    delete[] result; 
    return newResult;
}
return result;
}
4

4 に答える 4

3

率直に言って申し訳ありませんが、文字列を「フォーマット」するコードはひどいものです。

まず、符号なしの 64 ビット int 値を渡します。代わりに、これを符号付きの値としてフォーマットします。バナナを売っていると主張する場合、代わりにオオバコを顧客に与えるべきではありません.

しかし、さらに悪いことに、(クラッシュしていないときに) 返されるものが正しくないことさえあります。ユーザーが 0 を渡すと、何も返されません。ユーザーが 1000000 で合格した場合は 100,000 を返し、10000000 で合格した場合は 1,000,000 を返します。ええと、友達同士の数の 10 の因数は何ですか? ;)

これらは、クラッシュとともに、コードが実行するクレイジーなポインター演算の症状です。さて、バグに:

まず、「newResult」を割り当てると、バッファが非常に奇妙な状態になります。最初の nComma + nDigits バイトはランダムな値で、その後に NULL が続きます。次に、そのバッファーで strlen を呼び出します。その strlen の結果は、0 と nComma + nDigits の間の任意の数になる可能性があります。これは、nComma + nDigit 文字のいずれかに null バイトが含まれている可能性があり、strlen が途中で終了する可能性があるためです。つまり、コードはその時点以降は非決定論的です。

補足: なぜデバッグ ビルドで機能するのか知りたい場合は、コンパイラとランタイム ライブラリのデバッグ バージョンが、メモリを初期化することでバグの検出を支援しようとするためです。Visual C++ では、フィル マスクは通常 0xCC です。これにより、strlen() のバグがデバッグ ビルドで確実に隠蔽されました。

このバグを修正するのは非常に簡単です。バッファをスペースで初期化し、その後に NULL を入力するだけです。

char* newResult = new char[nComma+nDigits+1];
memset(newResult, ' ', nComma+nDigits);
newResult[nComma+nDigits]='\0';

しかし、もう1つバグがあります。1,152,921,504,606,846,975 になるはずの数値 1152921504606846975 をフォーマットしてみましょう。手の込んだポインター算術演算のいくつかが私たちに与えるものを見てみましょう:

memcpy(newResult + 25 - 3 - 0, result + 19 - 3, 3)
*(newResult + 25 - 4) = ','
memcpy(newResult + 25 - 6 - 1, result + 19 - 6, 3)
*(newResult + 25 - 8) = ','
memcpy(newResult + 25 - 9 - 2, result + 19 - 9, 3)
*(newResult + 25 - 12) = ','
memcpy(newResult + 25 - 12 - 3, result + 19 - 12, 3)
*(newResult + 25 - 16) = ','
memcpy(newResult + 25 - 15 - 4, result + 19 - 15, 3)
*(newResult + 25 - 20) = ','
memcpy(newResult + 25 - 18 - 5, result + 19 - 18, 3)
*(newResult + 25 - 24) = ','
memcpy(newResult + 25 - 21 - 6, result + 19 - 21, 3)

ご覧のとおり、最後の操作では、割り当てたバッファーの先頭の2 バイト前にデータがコピーされます。これは、常に 3 文字をコピーすると想定しているためです。もちろん、常にそうであるとは限りません。

率直に言って、FormatNumber のバージョンを修正する必要はないと思います。ポインター演算と計算はすべて、発生するのを待っているバグです。これが私が書いたバージョンです。必要に応じて使用できます。私はそれがはるかに正気だと思いますが、あなたのマイレージは異なる場合があります:

const char *StatThread::FormatNumber(UINT64 number) const
{
    // The longest 64-bit unsigned integer 0xFFFFFFFF is equal
    // to 18,446,744,073,709,551,615. That's 26 characters
    // so our buffer will be big enough to hold two of those
    // although, technically, we only need 6 extra characters
    // at most.
    const int buflen = 64;

    char *result = new char[buflen];
    int cnt = -1, idx = buflen;

    do
    {
        cnt++;

        if((cnt != 0) && ((cnt % 3) == 0))
            result[--idx] = ',';

        result[--idx] = '0' + (number % 10);
        number = number / 10;
    } while(number != 0);

    cnt = 0;

    while(idx != buflen)
        result[cnt++] = result[idx++];

    result[cnt] = 0;

    return result;
}

PS: 「10 分の 1 のオフ」ということは、読者への演習として残されています。

于 2012-10-07T08:57:52.153 に答える
1

ラインで

DWORD nDigits = ceil(log10((double)number));

100には3桁が必要ですが、log 100 = 2です。これは、に割り当てる文字数が少なすぎることを意味しますchar* newResult = new char[nComma+nDigits+1];。これは、ヒープセルの終わりが上書きされており、その結果、ヒープが破損していることを意味します。デバッグヒープの割り当てはより寛容である可能性があるため、クラッシュはデバッグモードでのみ発生します。

于 2012-10-07T02:42:28.817 に答える
0

ヒープの破損は通常、ヒープデータ構造の上書きによって発生します。境界チェックを適切に行わずに、「result」と「newResult」を頻繁に使用します。デバッグビルドを実行すると、アライメント全体が変更され、偶然にエラーは発生しません。

私は次のようなチェックを追加することから始めます:

DWORD nDigits = ceil(log10((double)number));
if(nDigits>=100){printf("error\n");exit(1);}
result[nDigits] = '\0';
于 2012-10-07T02:40:20.397 に答える
0

あなたのStatThread::PrintStat関数には2つのことがあります。

これは、ループ本体が複数回実行される場合のメモリリークです。delete[]以前の値を呼び出さずに、これらのポインターを再割り当てします。

while((BytesInserted<=fileSize.QuadPart)&&flag){
    ...
    CUnique = FormatNumber(nUnique);
    CInserted = FormatNumber(nInserted);
    ...
}

これは割り当て=または比較であると想定されてい==ますか?

if(BytesInserted=fileSize.QuadPart)
    flag=false;

編集して追加:

関数では、StatThread::FormatNumberこのステートメントはブロックの最後にnullターミネータを追加しますが、前の文字にはガベージが含まれている可能性があります(new割り当てられたメモリをゼロにしません)。サブシークエストの呼び出しはstrlen()、予期しない長さを返す可能性があります。

newResult[nComma+nDigits]='\0';
于 2012-10-07T02:42:40.880 に答える