snprintf()用于往buf中写入格式化的字符串。
想要正确地使用snprintf,需要对它的入参size和返回值的含义有准确的理解。否则会踩入深坑而不自知。
1.语法说明
函数声明:
int snprintf(char *str, size_t size, const char *format, ...);
参数说明:
size:
snprintf往buf中最多写入size个字节。注意,字符串结尾的’\0’也被计算在内。
返回值:
如果size足够大,那么函数返回值代表实际写入的字节数。(注意,字符串结尾的’\0’不算在内)
如果size太小导致字符串被截断,那么根据所使用glibc库的版本,
从glibc version 2.1(发布于1999-02-03)开始:函数返回值代表欲写入字符串的原本长度(注意,’\0’不算在内)也就是说,如果返回值 大于等于size,那表明输出的字符串被截断了。
glibc version 2.0.6(发布于1997-12-29):当字符串被截断时,函数返回 -1。
也就是说,对返回值进行检查最稳妥的形式是:
int ret = snprintf(buf, size, format, ...);
if (ret == -1 || ret >= size)
{
printf("输出被截断了!");
}
2.代码示例
#include <stdio.h>
#include <string.h>
#define SRC_STRING "123456789abcdef|-+"
int main()
{
char guard1[10] = {0};
// 注意看,我故意把buf的size设小一点。为了体现坑。
char buf[10] = {0};
char guard2[10] = {0};
int ret = 0;
int size = 0;
memset(guard1, '*', sizeof(guard1) - 1);
memset(guard2, '*', sizeof(guard2) - 1);
printf("guard1:%p buf:%p guard2:%p\n", guard1, buf, guard2);
printf("guard:[%s], buf[%s], guard2[%s]\n\n", guard1, buf, guard2);
size = 17;
ret = snprintf(buf, size, SRC_STRING);
printf("size=%d and sizeof(SRC_STRING) is %lu ret is %d\noutput is:[%s]\n\n",
size, sizeof(SRC_STRING), ret, buf);
memset(buf, 0, sizeof(buf));
size = 18;
ret = snprintf(buf, size, SRC_STRING);
printf("size=%d and sizeof(SRC_STRING) is %lu ret is %d\noutput is:[%s]\n\n",
size, sizeof(SRC_STRING), ret, buf);
memset(buf, 0, sizeof(buf));
size = 19;
ret = snprintf(buf, size, SRC_STRING);
printf("size=%d and sizeof(SRC_STRING) is %lu ret is %d\noutput is:[%s]\n\n",
size, sizeof(SRC_STRING), ret, buf);
printf("guard:[%s], buf[%s], guard2[%s]\n", guard1, buf, guard2);
return 0;
}
3.运行结果
tplink@ubuntu:~/testZone/snprintf_test$ gcc test.c
tplink@ubuntu:~/testZone/snprintf_test$ ./a.out
guard1:0x7fff123872e0 buf:0x7fff123872f0 guard2:0x7fff12387300
guard:[*********], buf[], guard2[*********]
size=17 and sizeof(SRC_STRING) is 19 ret is 18
output is:[123456789abcdef|] #实际写入了17字节:前16个字符+1个'\0'
size=18 and sizeof(SRC_STRING) is 19 ret is 18
output is:[123456789abcdef|-] #实际写入了18字节:前17个字符+1个'\0'
size=19 and sizeof(SRC_STRING) is 19 ret is 18
output is:[123456789abcdef|-+] #实际写入了19字节:18个字符+1个'\0'
guard:[*********], buf[123456789abcdef|-+], guard2[-+]
4.踩坑 & 总结
回去看代码代码示例,我故意把buf设为10个字节长,但由于往snprintf中传入的size分别为17、18、19,因此snprintf还是往buf中写入了min(size, strlen(SRC_STRING) + 1)个字节。也就是说发生了内存越界。
注意到guard2的起始地址在buf起始地址的16字节后,当size=19时,snprintf往buf开始的地址写入了18个字符+1个’\0’字符。于是guard2[0] = buf[16] = ‘-’; guard2[1] = buf[17] = ‘+’; guard2[3] = buf[18] = ‘\0’;
设想要是我们的guard2保存的是非常重要的数据,那么内存月节后将会导致不可预知的严重后果。
所以关于snprintf的使用,务必要明确size参数的含义。如果min(size, strlen(src_string) + 1)比buf的实际尺寸大,将会导致内存越界。切记切记。
注:上述关于参数size和返回值的说明,对函数vsnprintf()也同样适用。