std::to_chars 无内存分配开销,因它仅向用户提供的缓冲区写入数据,不进行动态分配、不构造字符串、不自动添加终止符,且需用户确保缓冲区足够并检查返回的错误码。

std::to_chars 为什么没有内存分配开销
因为 std::to_chars 只写入你提供的缓冲区,不 new、不 malloc、不构造 string,连 size 计算都由你控制。它只做一件事:把数字按指定进制塞进 char* 起始地址开始的连续内存里,写完返回一个迭代器告诉你写到哪了。
常见错误是传入太小的缓冲区——比如对 int64_t 调用 std::to_chars(buf, buf + 10, x),而最大十进制长度其实是 20(带符号)+1(负号),buf 不够就直接返回 std::to_chars_result{buf + 10, std::errc::value_too_large},不会崩溃但结果无效。
- 十进制 int32_t 最多需要 11 字节(-2147483648)
- 十进制 int64_t 最多需要 20 字节(-9223372036854775808)
- 十六进制 uint64_t 最多需要 16 字节(不带 0x 前缀)
- 务必检查返回值中的
ec字段,别只看指针位置
sprintf 为什么慢得明显
sprintf 要解析格式字符串、处理各种类型标记(%d/%.2f/%s)、逐字符判断宽度/精度/对齐、还要兼容 locale(比如千位分隔符)、最后还得手动加 \0 结尾——这些全是运行时分支和查表操作。哪怕只是 sprintf(buf, "%d", 123),也绕不开格式解析器。
更隐蔽的坑是:sprintf 默认按 locale 格式化,std::to_chars 永远用 C locale,所以两者结果在某些 locale 下根本不等价(比如德语 locale 的小数点会变成逗号)。如果你真要 locale-aware,std::to_chars 就不是替代方案。
立即学习“C++免费学习笔记(深入)”;
- 每次调用
sprintf都触发一次格式字符串状态机 - 浮点数路径尤其重:需转为科学计数法、处理舍入、生成指数部分
- 编译器几乎无法内联或优化
sprintf调用
std::from_chars 解析失败的典型表现
std::from_chars 不抛异常、不设 errno、也不保证输入全消费——它只解析“开头合法的数字部分”,遇到第一个非法字符就停。比如解析 "123abc",它会成功返回 123 并把指针停在 'a';而 atoi("123abc") 也会返回 123,但你完全不知道有没有多余字符。
真正容易出错的是边界情况:空字符串、只有空白、前导 +/- 后无数字、溢出(如 "99999999999999999999" 转 int)。这些都会让 ec 设为对应错误码,但很多人只检查指针是否移动,漏判 ec != std::errc{}。
- 输入
" 123"(带空格)→ 直接失败,ec == std::errc::invalid_argument - 输入
"-"或"+"→ 同样是invalid_argument,不是result.ptr没动的问题 - 溢出时
value输出参数未定义,必须先检查ec
性能差距到底来自哪几层
快不是玄学:第一层是零分配,第二层是无 locale 分支,第三层是整数路径完全避免除法——std::to_chars 对十进制整数用的是“双向写入+逆序翻转”策略,核心循环里全是加减位移,没有模 10 或除 10;而 sprintf 的 %d 必须反复做除法取余。
浮点数路径差异更大:std::to_chars 用 Ryu 算法(无查表、无大整数运算),sprintf 多数实现用的是较重的 Grisu 或 Dragon 算法变种,且依赖 malloc 临时缓冲。
- 整数 to_chars:O(log₁₀ n) 时间,纯位运算 + 内存拷贝
- sprintf %d:O(log₁₀ n) 时间,但常数项高 3–5 倍(除法指令 + 格式解析)
- 启用
-O2后,std::to_chars很可能被整个内联进调用者,sprintf几乎不可能
真正难的是写对——缓冲区大小算错、忽略 ec、混淆 locale 行为,这些问题比性能本身更常导致线上 bug。










