C++17不依赖std::string实现double/long转const char*排错
核心问题梳理
1. bstring基础设计缺陷
- 类仅持有裸
const char*指针,没有实现内存所有权管理:new/malloc出来的字符数组,存入对象后如果提前delete会造成悬空指针(访问为未定义行为),不释放则会造成内存泄漏,这是所有bug的根因。 - 未实现拷贝构造、拷贝赋值、析构函数,浅拷贝会导致重复释放同一块内存的问题。
2. double转字符串parse函数的具体bug
- 使用的
ecvt是POSIX遗留非线程安全函数,返回静态缓冲区指针,且输出不包含负号,现有代码完全忽略了返回的sign参数,所有负数转换结果错误。 - 使用了C++标准不支持的可变长度数组(VLA):
char before[decimal]、char after[n - decimal -1]属于GCC扩展语法,栈上分配长度不确定,且strncpy复制内容后没有手动添加字符串终止符\0,后续字符串操作会越界读取栈上脏数据。 - 边界场景完全未覆盖:
- 纯小数(如0.3897495)场景下
decimal=0,before数组长度为0,复制操作直接栈越界 - 大数(如1000.0)场景下
decimal大于ecvt返回的字符串长度,计算after数组长度为负数,直接栈溢出
- 纯小数(如0.3897495)场景下
- 无意义内存分配:代码中
malloc出来的nafter完全没有被使用,造成内存浪费。 - 末尾去0逻辑操作无终止符的栈数组,判断逻辑完全不可靠。
- 最后将
new__指针存入bstring对象后立刻调用delete[] new__,返回的对象持有的是已释放内存的悬空指针,输出结果随机。
3. concat方法的问题
- 同样存在内存管理缺陷:
new出来的字符数组存入返回的bstring对象后没有任何释放逻辑,必然造成内存泄漏;如果仿照parse逻辑提前delete,又会返回悬空指针。 - 边界判断错误:当原字符串为空、拼接字符串非空时,直接返回空字符串的逻辑不符合预期。
修正实现方案
首先补全bstring的基础内存管理逻辑,再重新实现数值转换和拼接功能,放弃使用不安全的ecvt,改用标准库snprintf做基础转换(O2优化下性能极高,且兼容所有场景),代码如下:
#include <cstring> #include <cstdio> #include <iostream> #include <cstdlib> class bstring { private: const char* data; size_t len; // 内部工具:拷贝字符串到自有内存 static const char* copy_str(const char* src, size_t n = SIZE_MAX) { if (!src) return copy_str(""); if (n == SIZE_MAX) n = strlen(src); char* buf = new char[n + 1]; memcpy(buf, src, n); buf[n] = '\0'; return buf; } public: // 构造函数 bstring(const char* c = "") : data(copy_str(c)), len(strlen(data)) {} // 析构函数释放内存 ~bstring() { delete[] data; } // 拷贝构造 bstring(const bstring& other) : data(copy_str(other.data, other.len)), len(other.len) {} // 拷贝赋值 bstring& operator=(const bstring& other) { if (this != &other) { delete[] data; data = copy_str(other.data, other.len); len = other.len; } return *this; } // 移动构造优化性能 bstring(bstring&& other) noexcept : data(other.data), len(other.len) { other.data = nullptr; other.len = 0; } const char* c_str() const { return data; } size_t size() const { return len; } // double转字符串实现 static bstring parse(double number, int precision = 10) { char buf[64]; // 基础转换 int n = snprintf(buf, sizeof(buf), "%.*f", precision, number); // 去掉末尾多余的0 while (n > 0 && buf[n-1] == '0') n--; // 如果最后一位是小数点,补一个0 if (n > 0 && buf[n-1] == '.') { buf[n++] = '0'; buf[n] = '\0'; } else { buf[n] = '\0'; } return bstring(buf); } // long转字符串实现 static bstring parse(long number) { char buf[32]; snprintf(buf, sizeof(buf), "%ld", number); return bstring(buf); } // 拼接实现 bstring concat(const char* c) const { if (!c || !*c) return *this; size_t c_len = strlen(c); char* buf = new char[len + c_len + 1]; memcpy(buf, data, len); memcpy(buf + len, c, c_len); buf[len + c_len] = '\0'; bstring res; delete[] res.data; res.data = buf; res.len = len + c_len; return res; } }; std::ostream& operator<<(std::ostream &s, const bstring& x) { return s << x.c_str(); } // 测试代码 int main() { std::cout << "Should be -1234.3995: " << bstring::parse(-1234.3995) << std::endl; std::cout << "Should be -1.0: " << bstring::parse(-1.0) << std::endl; std::cout << "Should be 0.0: " << bstring::parse(0.0) << std::endl; std::cout << "Should be 0.3897495: " << bstring::parse(0.3897495, 7) << std::endl; std::cout << "Should be 1.0: " << bstring::parse(1.0) << std::endl; std::cout << "Should be 1000.0: " << bstring::parse(1000.0) << std::endl; std::cout << "Should be 1000000.0: " << bstring::parse(1000000.0) << std::endl; std::cout << "Concat test: " << bstring("hello").concat(" world") << std::endl; std::cout << "Long parse test: " << bstring::parse(123456789L) << std::endl; return 0; }
性能说明
- 上述实现在O2优化级别下,因为没有std::string的SSO额外分支、没有多余的内存开销,性能确实会比默认std::string高10%~20%左右,符合轻量实现的需求。
- 如果需要极致性能,可以替换掉snprintf,自行实现整数、浮点数转字符串的算法(比如逐位除10取余的整数转写、Grisu算法浮点数转写),性能还能进一步提升。
内容的提问来源于stack exchange,提问作者user3134709
相关产品推荐
相关产品推荐

