专业编程基础技术教程

网站首页 > 基础教程 正文

C函数snprintf的用法和避免踩坑

ccvgpt 2024-08-05 12:15:19 基础教程 77 ℃

snprintf 函数是C语言中非常常用的一个函数,它的作用是格式化一段文本放到指定的缓存中。和它类似的还有sprintf, vsnprintf等。它们都完成类似的功能,但又有些区别,如sprintf 是不会检查输出缓存的长度的,需要调用者确保长度是足够的。vsnprintf 函数和snprintf 很像,但它是通过 va_list 来传入格式化参数,所以一般用于传入了可变的需要格式化字符串的函数内。而我们最常用的就是snprintf 了,因为它具有缓冲区的溢出保护。


C函数snprintf的用法和避免踩坑

snprintf函数的原型如下:

int snprintf(char *str, size_t size, const char *format, ...);

其中: str 传入输出缓冲区的地址;size指出输出缓冲区的大小;format 为传入的格式化字符串;其后的参数就是格式化的参数了。

该函数返回一个整数,表示格式化后的文本大小。通过这个返回值,我们可以做很多事情。也正是对这个返回值的理解不够或者使用错误,我们往往会踩一些坑。


最普通的用法是使用该函数来格式化一段文本,如:

char   szText[1024];
int    a=10, b=20;
snprintf(szText, sizeof(szText), "%d + %d = %d", a, b, a+b);


该函数具有缓冲区保护的功能,如果格式化输出内容超过了缓冲区的最大长度,那么输出字符串会被截断到缓冲区的最大长度。同时无论如何,字符串最后的结尾 ‘\0’ 是一定会有的,保证了输出的字符串不会溢出。所以这也是广大程序员喜欢使用这个函数的原因。


如果我们需要格式化一个具有多个元素的数组,那么这个时候就可以充分利用snprintf的返回值了。我们只需要反复调用snprintf 并移动缓冲区的起始位置就能实现。代码如下:

char   szText[1024];     // 输出的缓冲区
int   a[100] = {...};    // 假设会有代码逻辑填充这个数组的内容
int   n;    // 循环数组的下标
int   len = snprintf(szText, sizeof(szText), "a= {");     // 保存输出字符串的长度
for  (n = 0; n < 100; n++)
{
    len += snprintf(szText + len, sizeof(szText)-len, "%d, ", a[n]);
}
len += snprintf(szText + len, sizeof(szText)-len, "}");


这段代码看起来能正常工作,通常也确实可以正常工作,但是前提条件是 szText的缓冲区大小是足够大的。如果缓冲区的大小不够会发生什么情况?你不妨改小szText再试试。


那么为什么会出现问题呢?首先我们要深入理解一下snprintf 返回值的含义。按前面所述,返回值表示格式化后文本的大小,注意不是返回输出缓冲内的字符串长度。什么意思呢,也就是说,如果输出的缓冲足够大,大到可以容纳所有的输出文本,那么这个返回的长度恰恰就是输出缓存中的字符串长度。如果输出缓冲不够大,或者根本就没有传入输出缓冲这个参数呢?返回值就和实际的输出文本长度不一样了。为什么是这样?其实这个函数本来也是可以用来计算格式化所需要的最小缓存大小,并根据返回值来实际分配输出缓冲的内存。如下代码所示的用法:

int a = 10;
int b = 20;
char* pszOut = NULL;  // 输出缓存指针
int len;
len = snprintf(NULL, 0, "%d + %d = %d", a, b, a+b);  // 计算需要的输出长度。
pszOut = (char*)alloca(len+1); // 需要考虑结尾的0 战用一个字符
snprintf(pszOut, len+1, "%d + %d = %d", a, b, a+b);

有人会疑惑,为什么需要这样写?上面这个例子可能不明显。但是如果需要格式化的内容不固定,长度变化范围很大。那么就有可能不太好使用固定大小的缓冲。


理解了这个返回值的含义和用法,再回到上面那个循环调用的例子。我们需要加上对长度的校验就没有问题了:

char   szText[1024];     // 输出的缓冲区
int   a[100] = {...};    // 假设会有代码逻辑填充这个数组的内容
int   n;    // 循环数组的下标
int   len = snprintf(szText, sizeof(szText), "a= {");     // 保存输出字符串的长度
for  (n = 0; n < 100 && len < sizeof(szText); n++)
{
    len += snprintf(szText + len, sizeof(szText)-len, "%d, ", a[n]);
}
len += snprintf(szText + len, sizeof(szText)-len, "}");


如果有什么疑问,欢迎一起讨论。

最近发表
标签列表