snprintf的使用方法和陷阱

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()也同样适用。

  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值