我有一个应用程序,它使用snprintf和vsnprintf将字符串打印到缓冲区。目前,如果它检测到溢出,则附加一个>到字符串的末尾作为字符串被切断的标志并向stderr输出警告。我正试图找到一种方法让它在另一个缓冲区中恢复字符串[从它停止的地方]。
如果这是使用strncpy,那将很容易;我知道写了多少字节,所以我可以从*(p + bytes_written)开始下一个打印;但是,对于printf,我有两个问题;首先,格式化说明符可能在格式字符串中占用最终字符串中的更多或更少空间,其次,我的valist可能会被部分解析。
有没有人对此有一个简单的解决方案?
编辑:我应该澄清一点,我正在研究内存有限的嵌入式系统+没有动态分配[即,我不想使用动态分配]。我可以打印255个字节的消息,但不能再打印,尽管我可以打印尽可能多的消息。但是,我没有内存在堆栈上分配大量内存,而且我的print函数需要是线程安全的,所以我不能只分配一个全局/静态数组。答案 0 :(得分:2)
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 */
}
答案 1 :(得分:2)
我认为你不能做你正在寻找的东西(除了通过直接的方式将缓冲区重新分配到必要的大小并再次执行整个操作)。
你列出的原因是它的一些贡献者,但真正的杀手是格式化程序可能在空间用完时格式化参数,并且没有合理的方法重新启动它。
例如,假设缓冲区中剩余3个字节,格式化程序开始处理值-1234567
的“%d”转换。它会将“-1 \ 0”放入缓冲区,然后执行其他任何操作来返回您真正需要的缓冲区大小。
除了能够确定格式化程序正在处理哪个说明符之外,您还需要能够确定在第二轮中传递-1234567
而不是传入234567
。 1}}。我无视你想出一个合理的方法来做到这一点。
现在,如果有一个真正的原因,你不想从顶部重新启动操作,你可能会用{1}} / snprintf()
调用包含分解格式字符串的内容,仅发送一次只有一个转换说明符,并将该结果连接到输出缓冲区。你必须想出一些方法让包装器在重试时保持一些状态,以便它知道要从哪个转换规范中获取。
所以也许它在某种意义上是可行的,但看起来似乎要避免更简单的“完全重试”方案是一项非常多的工作。我可以看到也许(也许)在一个你没有动态分配更大缓冲区(可能是嵌入式系统)的系统上尝试这个。在这种情况下,我可能会认为所需要的是一个更简单/受限制的范围格式化程序,它没有vsnprintf()
格式化程序的所有灵活性并且可以处理重试(因为它们的范围更加有限)。
但是,伙计,我会非常努力地谈论任何人说这是一个要求。
修改强>
实际上,我接受了一些。如果您愿意使用自定义版本的printf()
(让我们称之为snprintf()
),我可以看到这是一个相对简单的操作:
snprintf_ex()
int snprintf_ex( char* s, size_t n, size_t skipChars, const char* fmt, ...);
(及其伴随函数,例如snprintf_ex()
)会将字符串格式化为提供的缓冲区(像往常一样),但会跳过输出第一个vsnprintf()
个字符。
使用编译器库中的源代码(或使用Holger Weiss' snprintf()
之类的东西)作为起点,你可能很容易解决这个问题。使用它可能看起来像:
skipChars
一个缺点(可能或可能不可接受)是格式化程序将从一开始就重复进行所有格式化工作 - 它只会丢弃它出现的第一个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
函数系列处理截断时会发生什么)。
答案 2 :(得分:0)
如果您使用的是POSIX-ish系统(我猜您可能是因为您提到了线程),那么一个不错的解决方案就是:
首先尝试使用snprintf
将字符串打印到单个缓冲区。如果它没有溢出,你就节省了很多工作。
如果这不起作用,请创建一个新线程和一个管道(使用pipe()
函数),fdopen
管道的写入端,并使用vfprintf
来编写串。从管道的读取端获取新线程read
并将输出字符串分解为255字节的消息。关闭管道并在vfprintf
返回后与线程连接。