fmt
fmt 源码分析:format_to_n 为什么返回比实际写入更多的长度
阅读有界输出 traits 和上游测试,分清总长度、返回迭代器、缺少终止符与 UTF-8 字节截断。
你将学会
- 把实际存储与逻辑输出分开
- 用哨兵字符暴露真实契约
- 明确示例证明到哪里
开始前需要
- 理解基础 C++ 值、引用、字符串与构建目标
- 能够区分文档预期与实际测量结果
追踪具体 fmt 行为,在输出、生命周期和验证边界明确的前提下选择集成方式。
先看结论
- result.size 是完整格式化长度,不是实际存储量。
- format_to_n 不会追加空字符终止符。
- 字节写入上限不是理解编码的预览策略,也不是 CPU 工作量上限。
把实际存储与逻辑输出分开
format_to_n 返回的 format_to_n_result 同时包含输出迭代器和未截断的完整输出长度。core.h 中的 vformat_to_n 创建带 fixed_buffer_traits 的迭代器缓冲区,执行格式化后返回 buf.out() 与 buf.count()。这个区别是刻意设计的:目标只有四个字节,并不代表格式化结果只长四个字节。
traits 对象分别记录写入限制和累计计数。limit 函数会把整个输入块长度计入 count_,但只返回剩余写入额度允许的部分。在裸指针特化中,直接目标空间耗尽后,缓冲区会切换到内部存储,继续完成格式化与计数路径,而不是把逻辑结果长度也截断。
用哨兵字符暴露真实契约
固定版本的 format_to_n 测试把十进制 12345 写入四字符数组的前三个位置,在第四个位置保留哨兵。测试预期 size 为 5、迭代器前进 3,完整四字节内容为 123x。API 文档明确不追加空字符终止符,因此不能据此把整个数组安全交给要求 C 字符串的消费者。
当限制为零时,API 仍能报告逻辑输出长度,同时不向目标写入任何字符。反过来,非零限制也不代表 Unicode 字符或字素边界。对于 char 类型的 UTF-8 数据,把一个三字节字符限制为两个输出字节,可能留下不完整序列;文本预览需要自己增加理解编码的截断策略。
明确示例证明到哪里
下面的 C++ 夹具通过哨兵和精确迭代器断言展示公开 API。配套编辑探针还覆盖零写入限制、三字节 UTF-8 输入,以及被移到运行期间的类型错误。这些例子足够小,能定位错误假设,又不会依赖远程输入或制造大规模内存消耗。
另一份独立 JavaScript 夹具只模拟块计数,不是 C++ 解析器,也不是原生 fmt 的替代实现。解释结果时应保留这个区别。无论教学模型还是有界原生夹具,都不能直接证明超大宽度、自定义 formatter、关闭异常的构建,以及所有输出迭代器的性能或正确性。
实施步骤
- 1
先用可见哨兵初始化缓冲区。
- 2
格式化一个明显超过写入限制的值。
- 3
分别检查迭代器、完整长度和未被改写的哨兵。
- 4
加入零限制与多字节案例,不把截断结果默认视为合法文本。
可复制示例
#include <fmt/format.h>
#include <cassert>
#include <string>
int main() {
char out[4] = {'x', 'x', 'x', 'x'};
const auto result = fmt::format_to_n(out, 3, "{}", 12345);
assert(result.out == out + 3);
assert(result.size == 5);
assert(std::string(out, 4) == "123x");
}常见问题
为什么只写三个字符却返回长度五?
长度字段统计完整格式化结果,返回迭代器则表示实际写入的那一部分,两者承担不同职责。
能直接对这段输出调用 strlen 吗?
不能仅凭这个 API 契约这样做。它不追加终止符;应使用已知长度,或在自己的包装中明确预留并写入终止符。
资料来源
- include/fmt/core.h来源核查 2026-09-08
- test/format-test.cc来源核查 2026-09-08
- doc/syntax.md来源核查 2026-09-08