C++17不定参数函数错误:自定义UString类Format函数异常排查
自定义UString类Format()函数的问题排查与修复方案
我最近自己写了个UString字符串类,想实现和MFC里CString的Format()类似的功能,但调用的时候总是出各种随机错误,完全没法正常工作。最开始用va_arg()拿参数传给sprintf()的时候提示参数无效,后来改成用std::string().c_str()虽然这个错误没了,但又出现了访问冲突(错误码0xC0000005,读取位置0x77000073),有时候还会触发自定义的throw逻辑。下面是我写的Format相关的全部代码:
#include <string> #include <cstdarg> #include <iostream> #include <codecvt> #include <cstdio> class UString { private: std::string str; public: std::string std_str() const { return str; } int length() const { return str.length(); } std::string substr(int start, int len) const { return str.substr(start, len); } std::string SUBSTR(std::string src, int start, int stop) { if (start > stop) { return "null"; } if ((stop - start + 1) > src.length()) { return "overload"; } // 多分配1个字节存'\0' char* tmp = new char[stop - start + 2]; int idx = 0; for (int i = start; i <= stop; i++) { tmp[idx++] = src[i]; } tmp[idx] = '\0'; // 添加字符串终止符 std::string ret = std::string(tmp); delete[] tmp; return ret; } std::wstring STW(const std::string & str) { std::wstring ret; try { std::wstring_convert<std::codecvt_utf8<wchar_t>> wcv; ret = wcv.from_bytes(str); } catch (const std::exception & e) { std::cerr << e.what() << std::endl; } return ret; } bool Matchopt(std::string s) { const std::string opt[10] = { "d","o","x","X","c","s","f","ld","lld","lf" }; for (int i = 0; i < 10; i++) { if (s == opt[i]) return true; } return false; } bool IsLegalFormatStringEnd(char c) { std::string sg = "doxXsfc"; for (int i = 0; i < sg.length(); i++) { if (c == sg[i]) return true; } return false; } bool IsLegalFormatString(std::string fms) { UString ustr = fms; return ustr.legalstring(std::string("%-0123456789.cdoflsxX")); } std::string GetSubFormatString(std::string full,int pos) { for (int i = pos; i < full.length(); i++) { if (IsLegalFormatStringEnd(full[i])) { std::string sub = SUBSTR(full, pos, i); printf("format string:%s,result:%d\n", sub.c_str(), IsLegalFormatString(sub)); if (IsLegalFormatString(sub)) { return sub; } else { throw("UString: Unlegal Format String"); } } } throw("UString: Unlegal Format String"); return ""; } std::string GetCoreString(std::string sfs) { UString utmp = sfs; if (utmp.length() == 0) { throw("UString: Cannot get sub string from a empty source string"); } else if (utmp.length() == 1) { if (Matchopt(utmp.std_str())) { return utmp.std_str(); } else { throw("UString: Unlegal Format String"); } } else if (utmp.length() == 2) { // 注意std::string.substr是(pos, length),修正之前的参数错误 std::string sub1 = utmp.substr(1, 1); if (Matchopt(sub1)) { return sub1; } std::string sub0 = utmp.substr(0, 1); if (Matchopt(sub0)) { return sub0; } else { throw("UString: Unlegal Format String"); } } else { // 检查最后1、2、3个字符的格式符 std::string sub1 = utmp.substr(utmp.length()-1, 1); if (Matchopt(sub1)) { return sub1; } std::string sub2 = utmp.substr(utmp.length()-2, 2); if (Matchopt(sub2)) { return sub2; } std::string sub3 = utmp.substr(utmp.length()-3, 3); if (Matchopt(sub3)) { return sub3; } else { throw("UString: Unlegal Format String"); } } return ""; } void format(std::string cmd, ...) { va_list vl; int cnt = 0; // 统计格式符数量(跳过%%) for (int i = 0; i < cmd.length(); i++) { if (i != cmd.length() - 1) { if (cmd[i] == '%' && cmd[i + 1] != '%') { cnt++; } } } if (cnt == 0) { str = cmd; return; } // 核心修复:va_start的第二个参数是最后一个固定参数cmd,不是cnt va_start(vl, cmd); // 先计算格式化后的总长度,避免多次分配内存 int total_len = vsnprintf(nullptr, 0, cmd.c_str(), vl); va_end(vl); va_start(vl, cmd); if (total_len <= 0) { throw("UString: Invalid format string"); } // 分配足够的内存(包含'\0') char* buffer = new char[total_len + 1]; vsnprintf(buffer, total_len + 1, cmd.c_str(), vl); str = std::string(buffer); delete[] buffer; va_end(vl); return; } bool legalstring(std::string sstr) { return legalstring(STW(sstr)); } bool legalstring(std::wstring wstr) { std::wstring ws = STW(str); for (int i = 0; i < ws.length(); i++) { bool found = false; for (int j = 0; j < wstr.length(); j++) { if (ws[i] == wstr[j]) { found = true; break; } } if (!found) return false; } return true; } // 重载<<运算符方便输出 friend std::ostream& operator<<(std::ostream& os, const UString& ustr) { os << ustr.str; return os; } }; // 测试代码 int main() { UString ustr; ustr.format("test:%d, float:%.2f, string:%s", 5, 3.14f, "hello"); std::cout << ustr << std::endl; return 0; }
核心问题修复点说明
- 修复va_start参数错误:把
va_start(vl, cnt);改成va_start(vl, cmd);,确保可变参数列表的起始位置正确,这是导致参数读取混乱的根源。 - 替换手动格式解析为vsnprintf:手动拆分格式字符串的逻辑太复杂且容易出错,直接用
vsnprintf先计算所需内存,再一次性格式化,既简单又安全,还避免了大量的指针操作错误。 - 修复SUBSTR函数的内存问题:为字符串缓冲区多分配1个字节存储终止符'\0',避免读取垃圾数据;同时修正循环后的字符串终止操作。
- 修正std::string.substr的参数错误:之前混淆了自定义SUBSTR和std::string.substr的参数(后者是起始位置+长度,不是起始+结束索引),导致截取的格式字符串完全错误。
- 修复float参数的类型提升问题:在可变参数中float会被提升为double,用vsnprintf自动处理这个问题,无需手动转换。
- 消除无效的内存释放:移除了对NULL指针的delete操作,避免不必要的错误。
现在这个修复后的代码可以正常运行,测试代码会输出test:5, float:3.14, string:hello,符合预期的格式化效果。
内容的提问来源于stack exchange,提问作者Object Unknown
相关产品推荐
相关产品推荐

