2009-10-01 62 views
9

以下代码会导致错误并杀死我的应用程序。这很有意义,因为缓冲区只有10个字节长,文本长度为22个字节(缓冲区溢出)。带缓冲区太小的sprintf_s

char buffer[10];  
int length = sprintf_s(buffer, 10, "1234567890.1234567890."); 

如何捕获此错误,以便报告而不是崩溃我的应用程序?

编辑:

阅读下面的评论我_snprintf_s去后。如果它返回-1值,那么缓冲区没有更新。

length = _snprintf_s(buffer, 10, 9, "123456789"); 
printf("1) Length=%d\n", length); // Length == 9 

length = _snprintf_s(buffer, 10, 9, "1234567890.1234567890."); 
printf("2) Length=%d\n", length); // Length == -1 

length = _snprintf_s(buffer, 10, 10, "1234567890.1234567890."); 
printf("3) Length=%d\n", length); // Crash, it needs room for the NULL char 
+0

传递的缓冲区大小和缓冲区大小减去一个是钝的,而且容易出错。您应该更喜欢下面描述的变体: length = _snprintf_s(buffer,_TRUNCATE,“1234567890.1234567890。”); 由于省略了第一个大小参数,因此编译器使用模板过载来推断大小。 _TRUNCATE是一个特殊的值,它可以完成它所说的事情。没有幻数,现在你的代码是安全的,可维护的,并且是一个很好的例子。 如果你喜欢这个评论和_snprintf_s,那么你应该选择我的答案,而不是危险的snprintf/_snprintf答案。 – 2014-12-17 22:40:06

回答

5

而不是sprintf_s,你可以使用snprintf(在Windows上另一个_snprintf)。

#ifdef WIN32 
#define snprintf _snprintf 
#endif 

char buffer[10];  
int length = snprintf(buffer, 10, "1234567890.1234567890."); 
// unix snprintf returns length output would actually require; 
// windows _snprintf returns actual output length if output fits, else negative 
if (length >= sizeof(buffer) || length<0) 
{ 
    /* error handling */ 
} 
+4

还有一个snprintf_s。 – Joe 2009-10-01 20:10:56

+2

注意:出于安全考虑,如果没有足够的空间,缓冲区的内容可能不会被空终止。 – Managu 2009-10-01 20:12:51

+2

@Managu:如果MS声称符合C99-它不会 - 断言将是假的; C99标准要求snprintf()以null结束字符串,除非字符串的长度为0. 7.19.6.5节:如果n为零,则不会写入任何内容......否则,n-1之外的输出字符将被丢弃而不是而不是写入数组,并且在实际写入数组的字符末尾写入空字符 。如果复制发生在重叠的对象 之间,则行为是不确定的。 – 2009-10-01 22:24:50

0

从MSDN:

sprintf_s和sprintf之间的另一个主要区别是,sprintf_s带长度参数指定在字符的输出缓冲区的大小。如果缓冲区对于正在打印的文本太小,则将缓冲区设置为空字符串,并调用无效参数处理程序。与snprintf不同,sprintf_s保证缓冲区将以空终止(除非缓冲区大小为零)。

所以你写的应该是正确的。

+4

默认的“无效参数处理程序”终止进程。 – 2009-10-01 19:49:47

+0

是真的,但是安装一个不容易,如果缓冲区太小,会导致sprintf_s返回-1 – stijn 2009-11-11 08:31:49

0

看起来像你写的MSVC的某种?

我认为sprintf_s的MSDN文档说它声明死亡,所以我不太确定你是否可以通过编程来捕获它。

正如LBushkin建议的那样,使用管理字符串的类更好。

16

这是设计。 sprintf_s的整个点以及*_s系列的其他功能都是为了捕获缓冲区溢出错误,并将它们视为先决条件违规。这意味着它们并非真正意味着可以恢复。这样做的目的只是为了发现错误 - 如果您知道该字符串对于目标缓冲区来说可能太大,则不应该调用sprintf_s。在这种情况下,首先使用strlen检查并决定是否需要修剪。

+0

我不同意。调用sprintf_s的目标缓冲区太小是完全合理的,只要您使用_TRUNCATE标志来指示。好吧,从技术上来说_TRUNCATE需要使用snprintf_s而不是sprintf_s,但我的观点大部分都是站得住脚的。 使用strlen往往不适用或不方便,但使用_TRUNCATE通常是微不足道的和适当的。 – 2015-11-16 23:05:16

+0

我认为使用'snprintf_s'是至关重要的区别,并不仅仅是技术性。 – 2015-11-17 02:52:34

+0

这是一个关键的区别,当然。但我认为你的回答看起来像功能家族不能截断,这可能是误导。 – 2015-11-18 17:05:20

0

请参阅TR24731的第6.6.1节,它是由Microsoft实施的ISO C Committee版本的功能。它提供了功能set_constraint_handler(),abort_constraint_handler()ignore_constraint_handler()的功能。

Pavel Minaev有评论认为微软的实施不符合TR24731提案(这是'Type 2 Tech Report'),所以你可能无法干预,或者你可能不得不做一些事情与TR指示应该完成的不同。为此,仔细检查MSDN。

+1

不幸的是,MSVC并没有完全实现TR24731 - 特别是,它没有专门实现你引用的函数(同样,它们的名字也以'_s'结尾 - 即'set_constraint_handler_s')。 – 2009-10-01 20:04:07

+1

但根据http://msdn.microsoft.com/en-us/library/ksazx244%28VS.80%29.aspx有一个函数_set_invalid_parameter_handler()函数,可用于更改中止程序的默认行为。 – 2009-10-02 02:26:41

5

这适用于VC++,比使用的snprintf(当然较安全_snprintf)更安全:

void TestString(const char* pEvil) 
{ 
    char buffer[100]; 
    _snprintf_s(buffer, _TRUNCATE, "Some data: %s\n", pEvil); 
} 

的_TRUNCATE标志表示该字符串应该被截断。在这种形式下,缓冲区的大小实际上并没有被传入,这(矛盾的!)是什么让它如此安全。编译器使用模板魔术来推断缓冲区大小,这意味着它不能被错误地指定(一个令人惊讶的常见错误)。这种技术可以应用到创建其他安全绳包装,在我的博客文章描述如下: https://randomascii.wordpress.com/2013/04/03/stop-using-strncpy-already/

+0

查看_snprintf_s的MSDN文档,看起来您已经在_snprintf_s调用中忘记了一个参数。这个参数应该出现在缓冲区和_TRUNCATE之间,被称为sizeOfBuffer – user1741137 2017-10-22 14:02:24

+1

我没有忘记一个参数 - 代码编译完全安全。您需要重新阅读文档。我正在使用_snprintf_s模板覆盖来告诉编译器推断缓冲区大小。 我已经看到了数百个程序员显式传递了缓冲区大小的地方,并且传递了*错误*大小。只有让编译器推断缓冲区大小,才能避免这种严重的错误。 我在我的解决方案中链接到的文章中提到了这种技术。强力推荐。 – 2017-10-23 16:07:40