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字节内存。
修复方案
- 为
bignum类添加析构函数,主动释放堆内存:
bignum::~bignum() { delete[] digits; }
- 补充实现拷贝构造函数,避免默认拷贝构造函数浅拷贝导致的双重释放风险:
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]; } }
- 可选优化现有赋值运算符的异常安全性:现有实现中如果
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
相关产品推荐
相关产品推荐

