C语言:跨平台环境下使用snprintf,vsnprintf系列函数要注意返回值的问题

2021-11-11 15:05:40 浏览数 (1)

标准C语言函数snprintf,vsnprintf系列函数可以向指定的缓冲区输出格式化打印的字符串。 如果指定的缓存区足够大,那么调用正常,返回值就是写入缓存区的字节长度(不含结尾'') 那么缓存区不够大的情况呢? 本文要说的是这系列函数的在缓存区长度不足以输出所有内容时的返回值在不同一编译器提供的实现表现是不同的。 我们用如下一段简单的测试代码来验证其返回值表现。

test_snprintf.c

代码语言:javascript复制
#include <stdio.h>
#include <errno.h>
#include <string.h>

int main()
{
	char buf[4] = { 0xff };
	int wsz = snprintf(buf,sizeof buf,"hello");
	printf("buf=%s,write size %dn",buf,wsz);
	if(wsz < 0)
	{
		printf("snprintf ERROR %d:%sn", errno, strerror(errno));
	}
	else if(wsz >= sizeof buf ) 
	{
		printf("snprintf buffer overflown");
	}
	/** 16进制输出缓冲区内容 */	
	for(int i = 0; i < sizeof buf;   i)
	{
		printf("%x,",buf[i]);
	}
	printf("n");	
}

上面代码中长度为4字节的buffer显然是无法完整输出hello,分别在MinGW(GCC 5.2.0)和MSVC(vs2015) 编译上面的代码。并运行它们,如下是运行结果。

因为输出的内容超过了buffer大小从运行结果看buf中的结果是不一致的,返回值也是不一样的。

在MSVC下返回的是待输出字符串('hello')的大小,而GCC下则是-1, 这不会吧?都是遵循C语言标准,为什么会出现不同的结果?

其实吧,标准这东西就是技术巨头们互相谈判妥协的结果。这两种不同的返回值都符合C语言标准,因为标准就是Microsoft这些巨头们制定的,当返回结果不一样又互不妥协的时候,那就把两种结果都写进标准吧。

下面是C语言标准库(C standard library)关于vsnprintf,snprintf函数的说明原文 https://en.cppreference.com/w/c/io/fprintf https://en.cppreference.com/w/c/io/vfprintf

下面的截图红框标注的部分为snprintf函数返回值定义:

翻译出来就是如果输入参数bufsz(缓冲区大小)为0,则返回应该写入buffer的长度(不含结尾’’),如果出错返回负值

下面的截图红框标注的部分为vsnprintf函数返回值定义:

翻译出来就是如果成功返回写入buffer的字符数量,如果出错返回负值。如果因为buffer长度限制而输出结果被截断,则函数返回应该写入buffer的字符数量,前提是这个buffer长度限制不是强制的 (if the limit was not imposed这一句我理解为如果buffer长度为0就是强制的 )。

把上面两段英文原文结合着看就差不多明白了。 GNU的实现的逻辑就是只要buffer长度不足,就认为是出错了,输出-1,然后把标准错误代码 errno 置为ERANGE(34)(不会把buffer最后一字节设置为‘’结尾,这样无结尾的字符串很危险了)。 MSC的实现逻辑是,不管buffer长度是多少,都不认为是出错,调用者可以通过返回值是不是超过了buffer的大小来判断是否完整输出(不论怎样会把buffer最后一字节设置为‘’结尾)。

这两种逻辑都是遵循C语言标准库定义的。

所以前面的测试代码进一步可以如下完善就可以在跨平台使用场景中更加安全的判断输出缓冲区是否不足了:

代码语言:javascript复制
#include <stdio.h>
#include <errno.h>
#include <string.h>

int main()
{
	char buf[4] = { 0xff };
	int wsz = snprintf(buf,sizeof buf,"hello");
	printf("buf=%s,write size %dn",buf,wsz);
	if(wsz < 0)
	{
		/** GNU C 下输出内容超过缓冲区大小要通过写入长度是否小于0以及errno是否为ERANGE来判断 */
		if(errno == EREANGE)
		{
			printf("snprintf buffer overflown");
		}else
		{
			/** 其他错误 */
			printf("snprintf ERROR %d:%sn", errno, strerror(errno));
		}
	}
	else if(wsz >= sizeof buf ) 
	{
		/** MSC 下输出内容超过缓冲区大小通过写入长度是否buffer长度来判断 */
		printf("snprintf buffer overflown");
	}		
}

0 人点赞