You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C++实现任意精度计算器时bignum类operator=内存泄漏问题排查

问题背景

使用C实现可处理任意大小数字的计算器,自定义bignum类突破C默认变量长度限制。重载>>运算符负责读取输入,采用Shunting Yard算法将输入解析为中缀表达式后计算。程序可正常运行,但Valgrind检测到内存泄漏问题。

Valgrind 报错信息
==765== HEAP SUMMARY:
==765==     in use at exit: 2 bytes in 1 blocks
==765==   total heap usage: 10 allocs, 9 frees, 74,880 bytes allocated
==765==
==765== 2 bytes in 1 blocks are definitely lost in loss record 1 of 1
==765==    at 0x483C583: operator new[](unsigned long) (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==765==    by 0x10E5A8: bignum::operator=(bignum const&) (bignum.cc:544)
==765==    by 0x110590: operator>>(std::istream&, bignum&) (bignum.cc:916)
==765==    by 0x10BB50: main (main.cc:110)
==765==
==765== LEAK SUMMARY:
==765==    definitely lost: 2 bytes in 1 blocks
==765==    indirectly lost: 0 bytes in 0 blocks
==765==      possibly lost: 0 bytes in 0 blocks
==765==    still reachable: 0 bytes in 0 blocks
==765==         suppressed: 0 bytes in 0 blocks
==765==
==765== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)
相关代码实现

重载 >> 运算符代码

// Input operator
istream& operator >> (istream &is, bignum &result)
{
    // Using de Shunting Yard's method
    stack<char> operations;
    queue<string> output;
    string input;
    
    // Pass the input to a string
    getline(is, input);

    // In case my string is empty
    if(input.empty())
        return is;

    // Parse the string
    for(size_t i = 0; i < input.size(); i++)
    {   
        if(isblank(input[i])){}

        // If a number is identified
        else if(isdigit(input[i]))
        {   
            size_t pos = i++;
            size_t len = 1;
            while(isdigit(input[i]))
            {
                len++;
                i++;
            }
            i--;
            output.push(input.substr(pos, len));
        }

        // If there's a minus sign
        else if(input[i] == '-')
        {
            size_t j = i;
            while(j != 0 && isblank(input[--j])){}

            // I've got a subtraction
            if(isdigit(input[j]) || input[j] == ')')
            {
                if(!operations.empty())
                {
                    while(operations.top() == '-' || operations.top() == '+' || operations.top() == '*' || operations.top() == '/')
                        output.push(string{operations.pull()});
                }   
                
                operations.push(input[i]);
            }
            // I've got a negative sign
            else
                output.push(string{"s"});
        }

        else if(input[i] == '+')
        {
            size_t j = i;
            while(j != 0 && isblank(input[--j])){}

            // I've got an addition
            if(isdigit(input[j]) || input[j] == ')')
            {
                if(!operations.empty())
                {
                    while(operations.top() == '-' || operations.top() == '+' || operations.top() == '*' || operations.top() == '/')
                        output.push(string{operations.pull()});
                }

                operations.push(input[i]);
            }
            // If there's a positive sign I do nothing
        }

        else if(input[i] == '*' || input[i] == '/')
        {   
            if(!operations.empty())
            {
                while(operations.top() == '*' || operations.top() == '/')
                    output.push(string{operations.pull()});
            }

            operations.push(input[i]);
        }

        // If there's an opening parenthesis
        else if(input[i] == '(')
            operations.push(input[i]);

        // If there's a closing parenthesis
        else if(input[i] == ')')
        {
            if(operations.empty())
            {   
                cout << "Syntax Error" << endl;
                return is;
            }
            
            while(!operations.empty() && operations.top() != '(')
                output.push(string{operations.pull()});

            if(!operations.empty())
                operations.pull();
            else
            {   
                cout << "Syntax Error" << endl;
                return is;
            }
        }
    }

    if(!operations.empty())
    {
        if(operations.top() == '(' && output.empty())
        {
            cout << "Syntax Error" << endl;
            return is;
        }
        else
        {   
            if(output.empty())
            {
                cout << "Syntax Error" << endl;
                return is;
            }
            else
            {   
                while(!operations.empty())
                    output.push(string{operations.pull()});
            }
        }
    }

    // I solve the output
    string aux;
    short_t sign = 0;
    stack<bignum> numbers;

    while(!output.empty())
    {
        aux = output.pull();

        if(isdigit(aux[0]))
        {
            numbers.push(bignum(aux, sign));
            sign = 0;
        }
        else if(aux[0] == 's')
            sign++;
        else if(aux[0] == '+')
        {   
            if(numbers.length() < 2)
            {   
                cout << "Syntax Error" << endl;
                return is;
            }
            result = numbers.pull();
            result = numbers.pull() + result;
            numbers.push(result);
        }
        else if(aux[0] == '-')
        {   
            if(numbers.length() < 2)
            {   
                cout << "Syntax Error" << endl;
                return is;
            }
            result = numbers.pull();
            result = numbers.pull() - result;
            numbers.push(result);
        }
        else if(aux[0] == '*')
        {   
            if(numbers.length() < 2)
            {   
                cout << "Syntax Error" << endl;
                return is;
            }
            result = numbers.pull();
            result = numbers.pull() * result;
            numbers.push(result);
        }
        else if(aux[0] == '/')
        {   
            if(numbers.length() < 2)
            {   
                cout << "Syntax Error" << endl;
                return is;
            }
            result = numbers.pull();
            result = numbers.pull() / result;
            numbers.push(result);
        }
    }

    if(!numbers.empty())
        result = numbers.pull();
    else
    {
        cout << "Syntax Error" << endl;
        return is;
    }

    return is;
}

赋值运算符实现代码

// Constructor por copia
bignum& bignum::operator = (const bignum &right)
{   
    // Verifico si los bignum a igualar son distintos
    if(&right != this)
    {   
        // Borro el puntero de this
        delete[] digits;

        // En caso de que right este apuntando a NULL
        if(!right.digits)
        {   
            type = STANDARD;
            digits = NULL;
            size = 0;
            sign = 0;
        }

        else
        {   
            type = right.type;
            digits = new short_t[right.size];
            size = right.size;
            sign = right.sign;

            // Copio los valores del arreglo right en this
            for(size_t i = 0; i < size; i++)
                digits[i] = right.digits[i];
        }
    }

    // Devuelvo un puntero a mi bignum this
    return *this;
}

bignum 类定义

class bignum
{
private:
    multiplication_algorithm_t type;
    short_t *digits;
    short_t sign;
    size_t size;
内存泄漏触发原因
  • 核心原因是bignum类违反了C++的三五法则,仅自定义了拷贝赋值运算符,未实现对应的析构函数释放digits指针指向的堆内存。当bignum对象生命周期结束时,其内部通过new[]申请的数字存储数组不会被自动回收,最终造成内存泄漏。
  • Valgrind统计显示10次堆申请仅有9次释放,缺失的1次释放对应程序退出前存储最终计算结果的bignum对象的digits数组,正好匹配泄漏的2字节内存。
修复方案
  1. 为bignum类添加析构函数,主动释放堆内存:
bignum::~bignum()
{
    delete[] digits;
}
  1. 补充实现拷贝构造函数,避免默认拷贝构造函数浅拷贝导致的双重释放风险:
bignum::bignum(const bignum& right)
{
    type = right.type;
    size = right.size;
    sign = right.sign;
    if (!right.digits) {
        digits = nullptr;
        return;
    }
    digits = new short_t[size];
    for (size_t i = 0; i < size; ++i) {
        digits[i] = right.digits[i];
    }
}
  1. 可选优化现有赋值运算符的异常安全性:现有实现中如果new[]抛出异常,会导致当前对象的digits已经被释放但新内存未申请成功,对象处于无效状态,可改用copy-and-swap写法规避该问题:
bignum& bignum::operator=(bignum right)
{
    std::swap(type, right.type);
    std::swap(digits, right.digits);
    std::swap(size, right.size);
    std::swap(sign, right.sign);
    return *this;
}

内容的提问来源于stack exchange,提问作者Sergio Lee

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.28 06:36:05