prev up next   top/contents search

comp.lang.c FAQ 列表· 问题 12.21

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/nullNUL代替临时文件。)

如果缓冲区可能不够大,您将不想调用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


prev up next   contents search
关于此 FAQ 列表   关于 Eskimo   搜索   反馈   版权

Eskimo North 托管