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 字符中的任何一个都可能包含空字节,这将导致 strlen 过早终止。换句话说,在那之后代码是不确定的。

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

修复这个错误非常简单:只需用空格初始化缓冲区,然后是 NULL。

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

但是还有一个错误。让我们尝试将数字 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;
}

PS:“关闭 10 倍”的东西留给读者作为练习。

于 2012-10-07T08:57:52.153 回答
1

在线上

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

100 需要三个数字,但 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您的功能中有两件事。

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

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

这应该是分配=还是比较==

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

编辑添加:

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

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