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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.14 07:26:42