我不认为你可以做你正在寻找的事情(除了通过将缓冲区重新分配到必要大小并再次执行整个操作的直接方法)。
您列出的原因是造成此问题的几个原因,但真正的杀手是格式化程序可能在格式化参数时空间不足,并且没有合理的方法来重新启动它。
例如,假设缓冲区中还剩下 3 个字节,格式化程序开始对值 -1234567 进行“%d”转换。它会将“-1\0”放入缓冲区,然后执行它需要执行的任何其他操作以返回您真正需要的缓冲区大小。
除了您能够确定格式化程序正在处理的说明符之外,您还需要能够弄清楚,您需要在第二轮传递234567,而不是传递-1234567。我不同意你想出一个合理的方法来做到这一点。
现在,如果您确实有不想从顶部重新启动操作的原因,您可能可以使用分解格式字符串的内容来包装 snprintf()/vsnprintf() 调用,只发送一个转换说明符一次并将结果连接到输出缓冲区。您必须想出一些方法让包装器在重试时保持某种状态,以便它知道从哪个转换规范中获取。
因此,从某种意义上说,它可能是可行的,但要避免更简单的“完全重试”方案,似乎确实需要做大量的工作。我可以看到也许(也许)在你没有动态分配更大缓冲区的奢侈的系统上尝试这个(也许是嵌入式系统)。在这种情况下,我可能会争辩说,需要一个更简单/受限制范围的格式化程序,它不具备 printf() 格式化程序的所有灵活性并且可以处理重试(因为它们的范围更有限)。
但是,伙计,我会非常努力地向那些说这是一项要求的人讲道理。
编辑:
实际上,我收回了其中的一部分。如果您愿意使用自定义版本的snprintf()(我们称之为snprintf_ex())我可以看到这是一个相对简单的操作:
int snprintf_ex( char* s, size_t n, size_t skipChars, const char* fmt, ...);
snprintf_ex()(及其配套函数,如vsnprintf())会将字符串格式化到提供的缓冲区中(像往常一样),但会跳过输出第一个skipChars字符。
您可以使用编译器库中的源代码(或使用 Holger Weiss' snprintf() 之类的东西)作为起点,非常容易地进行装配。使用它可能看起来像:
int bufSize = sizeof(buf);
char* fmt = "some complex format string...";
int needed = snprintf_ex( buf, bufSize, 0, fmt, arg1, arg2, etc, etc2);
if (needed >= bufSize) {
// dang truncation...
// do whatever you want with the truncated bits (send to a logger or whatever)
// format the rest of the string, skipping the bits we already got
needed = snprintf_ex( buf, bufSize, bufSize - 1, fmt, arg1, arg2, etc, etc2);
// now the buffer contains the part that was truncated before. Note that
// you'd still need to deal with the possibility that this is truncated yet
// again - that's an exercise for the reader, and it's probably trickier to
// deal with properly than it might sound...
}
一个缺点(可能会或可能不会被接受)是格式化程序会从一开始就重新进行所有格式化工作 - 它只会丢弃它出现的第一个 skipChars 字符。如果我必须使用这样的东西,我认为这几乎肯定是可以接受的(当有人使用标准的snprintf() 系列函数处理截断时会发生这种情况)。