达到限制后恢复?nprintf

我有一个应用程序,使用snprintf和vsnprintf将字符串打印到缓冲区。 目前,如果它检测到溢出,它会在字符串的末尾添加一个>作为字符串被切断的标志,并向stderr输出警告。 我正试图找到一种方法让它在另一个缓冲区中恢复字符串[从它停止的地方]。

如果这是使用strncpy,那将很容易; 我知道写了多少字节,所以我可以从*(p + bytes_written)开始下一个打印; 但是,对于printf,我有两个问题; 首先,格式化说明符可能在格式字符串中占用最终字符串中的更多或更少空间,其次,我的valist可能会被部分解析。

有没有人对此有一个简单的解决方案?

编辑:我应该澄清我正在研究内存有限的嵌入式系统+没有动态分配[即,我不想使用动态分配]。 我可以打印255个字节的消息,但不能再打印,尽管我可以打印尽可能多的消息。 但是,我没有内存在堆栈上分配大量内存,而且我的print函数需要是线程安全的,所以我不能只分配一个全局/静态数组。

我不认为你可以做你正在寻找的东西(除了通过直接的方式将缓冲区重新分配到必要的大小并再次执行整个操作)。

你列出的原因是它的一些贡献者,但真正的杀手是格式化程序可能在空间用完时格式化参数,并且没有合理的方法来重新启动它。

例如,假设缓冲区中剩余3个字节,并且格式化程序开始处理值为-1234567的“%d”转换。 它会将“-1 \ 0”放入缓冲区,然后执行其他任何操作来返回您真正需要的缓冲区大小。

除了能够确定格式化程序正在使用哪个说明符之外,您还需要能够-1234567在第二轮中传递-1234567而不是在234567传递。 我无视你想出一个合理的方法来做到这一点。

现在,如果有一个真正的原因你不想从顶部重新启动操作,你可能会用一些分解格式字符串的东西包装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()系列函数处理截断时会发生什么)。

C99函数snprintf()vsnprintf()都返回使用所有参数打印整个格式字符串所需的字符数。

如果您的实现符合C99,您可以创建一个足够大的数组用于输出字符串,然后根据需要处理它们。

 int chars_needed = snprintf(NULL, 0, fmt_string, v1, v2, v3, ...); char *buf = malloc(chars_needed + 1); if (buf) { snprintf(buf, chars_needed + 1, fmt_string, v1, v2, v3, ...); /* use buf */ free(buf); } else { /* no memory */ } 

如果你在POSIX-ish系统上(我猜你可能是因为你提到了线程),一个很好的解决方案是:

首先尝试使用snprintf将字符串打印到单个缓冲区。 如果它没有溢出,你就节省了很多工作。

如果这不起作用,创建一个新线程和一个管道(使用pipe()函数), fdopen的写入端,并使用vfprintf写入字符串。 从管道的读取端read新线程,并将输出字符串分解为255字节的消息。 关闭管道并在vfprintf返回后与线程vfprintf