仅在发布模式下堆积损坏

时间:2012-10-07 02:21:23

标签: c++ visual-c++

这是一个打印线程,用于打印当前正在运行的程序的统计信息

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 
}

这是FormatNumber,它返回一个指向char数组的指针

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;
}

真正奇怪的是,由于堆损坏,它仅在发布模式下崩溃,但在调试模式下运行顺利。我已经到处检查过,发现没有明显的内存泄漏,甚至内存泄漏检测器也这么说。

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 个答案:

答案 0 :(得分:3)

很抱歉直言不讳,但“格式化”字符串的代码是可怕

首先,传入一个无符号的64位int值,将其格式化为有符号值。如果您声称要出售香蕉,则不应该为您的客户提供大蕉。

但更糟糕的是,你所返回的东西(当你没有崩溃时)甚至是不对的。如果用户传入0,那么你根本不会返回任何内容。如果用户传入1000000,则返回100,000,如果他传入10000000,则返回1,000,000。哦,对于朋友之间的一些数字,有什么因素是10? ;)

这些以及崩溃是你的代码所做的疯狂指针算术的症状。现在,对于错误:

首先,当您分配'newResult'时,您将缓冲区置于非常奇怪的状态。第一个nComma + nDigits字节是随机值,后跟NULL。然后在该缓冲区上调用strlen。该strlen的结果可以是介于0和nComma + nDigits之间的任何数字,因为任何一个nComma + nDigit字符都可能包含空字节,这将导致strlen过早终止。换句话说,在那之后代码是非确定性的。

旁注:如果你很好奇为什么它在调试版本中有效,那是因为编译器和运行时库的调试版本试图通过为你初始化内存来帮助你捕获bug。在Visual C ++中,填充掩码通常为0xCC。这确保了你的strlen()中的错误在调试版本中被掩盖了。

修复该bug非常简单:只需使用空格初始化缓冲区,然后使用NULL。

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

但还有一个bug。让我们尝试格式化数字1152921504606846975,它应该变成1,152,921,504,606,846,975。让我们看看一些花哨的指针算术运算给我们的东西:

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;
}

P.S。:“关闭了10倍”的东西留给了读者。

答案 1 :(得分:1)

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

你需要三位数为100,但是记录100 = 2.这意味着你为char* newResult = new char[nComma+nDigits+1];分配了一个太少的字符。这意味着堆单元的末尾被覆盖,这导致您看到的堆损坏。调试堆分配可能更宽容,这就是崩溃仅在调试模式下的原因。

答案 2 :(得分:0)

堆损坏通常是由覆盖堆数据结构引起的。没有良好的边界检查,有很多使用“结果”和“newResult”。当您进行调试构建时,整个对齐会发生更改,并且偶然会发生错误。

我首先要添加这样的支票:

DWORD nDigits = ceil(log10((double)number));
if(nDigits>=100){printf("error\n");exit(1);}
result[nDigits] = '\0';

答案 3 :(得分:0)

StatThread::PrintStat函数中的两件事。

如果循环体执行多次,则这是内存泄漏。您可以重新分配这些指针,而无需为之前的值调用delete[]

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

这应该是作业=还是比较==

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

编辑以添加:

StatThread::FormatNumber函数中,此语句在块的末尾添加一个空终止符,但前面的字符可能包含垃圾(new不分配内存)。对strlen()的子请求调用可能会返回意外的长度。

newResult[nComma+nDigits]='\0';