Q我如何知道任意sprintf调用所需的缓冲区空间有多大?我如何避免目标缓冲区溢出?sprintf?
A当使用的格式字符串sprintf已知且相对简单时,您有时可以通过临时方式预测缓冲区大小。如果格式包含一个或两个%s,您可以自己计算格式字符串中的固定字符(或让sizeof为您计算),然后加上调用strlen插入的字符串的结果。例如,要计算调用
sprintf(buf, "You typed \"%s\"", answer);所需的缓冲区大小,您可以这样写:
int bufsize = 13 + strlen(answer); or int bufsize = sizeof("You typed \"%s\"") + strlen(answer);后面跟着
char *buf = malloc(bufsize); if(buf != NULL) sprintf(buf, "You typed \"%s\"", answer);您可以使用类似的代码保守地估计%d展开所需的大小:
#include <limits.h> char buf[(sizeof(int) * CHAR_BIT + 2) / 3 + 1 + 1]; sprintf(buf, "%d", n);此代码计算数字的基数为 8 的表示形式所需的字符数;基数为 10 的展开保证占用相同或更少的空间。(+2如果大小不是 3 的倍数,则处理截断,而+1+1留出了一个前导-和一个尾随\0的空间。)当然,对于long int,可以使用类似的技巧,并且相同的缓冲区也可以与%u, `%o`,而`%x`格式一起使用。
当格式字符串更复杂,或者直到运行时才知道时,预测缓冲区大小就变得像重新实现sprintf一样困难,因此容易出错(且不推荐)。有时建议的最后一种技术是使用fprintf将相同的文本打印到临时文件,然后查看fprintf的返回值或文件大小(但请参见问题 19.12)。(不可否认,为此应用程序使用临时文件很笨拙且不优雅,[脚注] 但除了编写完整的sprintf格式解释器之外,这是唯一可移植的解决方案。如果您的系统提供了一个,您可以使用 null 或“黑洞”设备,例如/dev/null或NUL代替临时文件。)
如果缓冲区可能不够大,您将不想调用sprintf,除非有保证缓冲区不会溢出并覆盖内存的其他部分。如果格式字符串已知,您可以通过使用%sN%.s(其中 N 是某个数字)或%.*s(另请参见问题 12.10)来限制的扩展。
为了避免溢出问题,您可以使用长度受限版本的sprintf,即snprintf。其用法如下:
snprintf(buf, bufsize, "You typed \"%s\"", answer);snprintf已经在多个 stdio 库(包括 GNU 和 4.4bsd)中可用多年。它最终在 C99 中标准化。
另外一个附加的好处是,C99 的snprintf提供了一种预测任意sprintf调用所需大小的方法。C99 的snprintf返回它本应放入缓冲区但由于空间不足而未放入的字符数,而不仅仅是它实际放入的字符数。此外,它可以与 null 指针、缓冲区大小为 0 和 null 指针作为目标缓冲区一起调用。因此,调用
nch = snprintf(NULL, 0, fmtstring, /* other arguments */ );将计算完全格式化字符串所需的字符数。有了这个数字(nch),您可以分配一个足够大的缓冲区,然后进行第二次snprintf调用来填充它。
还有一个选项是(非标准)asprintf函数,存在于各种 C 库中,包括 bsd 和 GNU 的库,它格式化(并返回指向)一个malloced 缓冲区,如下所示:
char *buf; asprintf(&buf, "%d = %s", 42, "forty-two"); /* now buf points to malloc'ed space containing formatted string */
其他链接: asprintf 的示例实现
参考:C9X Sec. 7.13.6.6