既然我们有snprintf,为什么我们没有snscanf?

时间:2013-08-21 22:23:29

标签: c posix

我有snprintf它可以避免缓冲区溢出,但为什么没有名为snscanf的函数?

代码:

int main()
{
     char * src = "helloeveryone";
     char buf1[5];
     sscanf(src,"%s",buf1); // here is a  array out of bounds

}

所以,我认为还需要snscanf。为什么我们只有snprintf

6 个答案:

答案 0 :(得分:11)

有争议的(和可选的)附件K到C11增加了一个sscanf_s函数,它在指针参数之后采用类型rsize_t(也在附件K中定义)的附加参数,指定了指向数组。无论好坏,这些功能都没有得到广泛支持。通过将大小放在转换说明符中,您可以获得相同的结果,例如

char out[20];
sscanf(in, "%19s", out);

但如果目标对象的大小在运行时可能会有所不同,那么这很容易出错并且容易出错(您必须使用snprintf以编程方式构造转换说明符)。请注意,转换说明符中的字段宽度是要读取的最大输入字符数,sscanf也会为%s转换写入终止空字节,因此您传递的字段宽度必须严格小于目标对象的大小。

答案 1 :(得分:9)

不需要snscanf(),因为没有写入第一个缓冲区参数。 snprintf()中的缓冲区长度指定写入的缓冲区大小:

char buffer[256];

snprintf(buffer, sizeof(buffer), "%s:%d", s, n);

sscanf()的相应位置的缓冲区是以空字符结尾的字符串;不需要明确的长度,因为你不打算写它(在C99和C11中它是const char * restrict buffer

char buffer[256];
char string[100];
int n;
if (sscanf(buffer, "%s %d", string, &n) != 2)
    ...oops...

在输出中,您应该指定字符串的长度(尽管如果使用%s而不是%99s或者任何严格合适的话,您可能占多数):< / p>

if (sscanf(buffer, "%99s %d", string, &n) != 2)
    ...oops...

如果您可以%*s尽可能使用snprintf(),那将是很好/有用的,但您不能 - sscanf()*表示'做'不分配扫描值',而不是长度。请注意,您不会写snscanf(src, sizeof(buf1), "%s", buf1),尤其是因为您可以在一次通话中拥有多个%s转换规范。编写snscanf(src, sizeof(buf1), sizeof(buf2), "%s %s", buf1, buf2)没有任何意义,尤其是因为它在解析varargs列表时留下了不可解决的问题。如果snscanf(src, "%@s %@s", sizeof(buf1), buf1, sizeof(buf2), buf2)这样的符号可以避免在格式字符串中指定字段大小(减1),那将会很不错。不幸的是,你现在不能用sscanf() et al做到这一点。

ISO / IEC 9899:2011的附件K(以前为TR24731)提供了sscanf_s(),它确实需要字符串的长度,可以用作:

if (sscanf_s(buffer, "%s %d", string, sizeof(string), &n) != 2)
    ...oops...

(感谢R..提醒我这个理论选项 - 理论上是因为只有Microsoft实现了'安全'功能,而且他们并没有完全按照标准要求实现它们。)

请注意§K.3.3公共定义<stddef.h> 说:'...类型为rsize_t,类型为size_t 385)'(和脚注385说:'参见<stdint.h>.中的RSIZE_MAX宏的描述'这意味着事实上你可以通过size_t而不需要演员 - 只要传递的值在RSIZE_MAX<stdint.h>定义的范围内。(一般意图是RSIZE_MAX是一个较大的数字但小于SIZE_MAX。有关详细信息,请阅读2011年标准,或从Open Standards网站获得TR 24731。)

答案 2 :(得分:3)

sscanf(s, format, ...)中,扫描的字符数组为const char *。没有s的写作。 s[i]为NUL时扫描停止。很少需要n参数作为扫描的辅助限制。

sprintf(s, format, ...)中,数组s是目的地。 snprintf(s, n, format, ...)确保数据无法在s[n]及以后的数据中使用。


sscanf() 转换说明符的标记扩展名有用,因此可以在编译时轻松指定限制。 (今天,下面可以用动态格式或sscanf(src,"%4s",buf1)以繁琐的方式完成。)

// This is a proposed idea for C. - Not valid code today.
sscanf(src, "%!s", sizeof(buf1), buf)

此处!会告诉sscanf()读取size_t变量的大小限制即将发生的字符串。也许在C17?


今天有效的繁琐方法。

char * src = "helloeveryone";
char buf1[5];
char format[1+20+1+1];
sprintf(format, "%%" "%zu" "s", sizeof(buf1) - 1);
sscanf(src, format, buf1);

答案 3 :(得分:1)

为什么不尝试fgets()(使用标准输入文件stdin)?

fgets()可让您指定缓冲区的最大大小。

  

(在接下来的所有内容中,我将使用兼容的标准 ISO C99   语法。)

因此,您可以编写此代码:

#include <stdio.h>
#define MAXBUFF 20 /* Small just for testing... */
int main(void) {
  char buffer[MAXBUFF+1]; /* Add 1 byte since fgets() inserts '\0' at end */
  fgets(buffer, MAXBUFF+1, stdin);
  printf("Your input was: %s\n", buffer);
  return 0;
}

fgets()stdin读取最多MAXBUFF字符,
这是标准输入(即:键盘) 结果保存在数组buffer中 如果找到'\ n'字符,则读数停止,'\ n'也保存在buffer中(作为最后一个字符)。此外,buffer的末尾始终添加'\ 0',因此需要足够的存储空间 您可以使用fgets()后跟sscanf()的组合来处理字符串:

  char buffer[MAXBUFF+1];
  fgets(buffer, MAXBUFF+1, stdin); /* Plain read */
  int x; float f;
  sscanf(buffer, "%d %g", &x, &f); /* Specialized read */

因此,您有一个“安全”scanf()的方法。

注意: 这种方法存在潜在问题。如果fgets()在获得行尾字符'\ n'之前达到MAXBUFF字符,则输入的其余部分将不会被丢弃,并且它将被视为下一个键盘阅读 因此,必须添加 flush 机制,实际上非常简单:

while(getchar()!'\n') 
    ; /* Flushing stdin... */

但是:如果您只是在fgets()行之后添加最后一段代码, 每次输入少于MAXBUFF个字符时,用户将被强制两次按 ENTER 两次。最糟糕的是:这是最典型的情况!

要解决这个新问题,请注意一个简单的逻辑条件,完全等同于 字符'\ n'未到达 这一事实如下:< / p>

(buffer[MAXBUFF - 1] != '\0') && (buffer[MAXBUFF - 1] != '\n')

(证明它!)

因此,我们写道:

fgets(buffer, maxb+1, stdin);
if ((buffer[MAXBUFF - 1] != '\0') && (buffer[MAXBUFF - 1] != '\n'))
     while(getchar() != '\n')
       ;

需要最后的触摸:因为数组缓冲区可能有garbadge,
似乎需要某种初始化。
但是,让我们观察一下只需要清除位置[MAXBUFF - 1]

char buffer[MAXBUFF + 1] = { [MAXBUFF - 1] = '\0' }; /* ISO C99 syntax */

最后,我们可以在快速宏中收集所有这些事实,如此程序显示:

#include <stdio.h>
#define safe_scanf(fmt, maxb, ...) { \
    char buffer[maxb+1] = { [maxb - 1] = '\0' }; \
    fgets(buffer, maxb+1, stdin); \
    if ((buffer[maxb - 1] != '\0') && (buffer[maxb - 1] != '\n')) \
        while(getchar() != '\n') \
           ; \
    sscanf(buffer, fmt, __VA_ARGS__); \
  }
#define MAXBUFF 20     

int main(void) {
  int x; float f;      
  safe_scanf("%d %g", MAXBUFF+1, &x, &f);
  printf("Your input was: x == %d\t\t f == %g",  x, f);
  return 0;
}

在宏< 中使用了 可变参数个数的机制,
ISO C99 规范下:Variadic macros
__VA_ARGS__替换变量参数列表 (我们需要可变数量的参数来模仿类似scanf()的行为。)

注意: 宏体被封闭在一个 {} 的块中。这并不完全令人满意,而且很容易改进,但它是另一个主题的一部分......
特别是,宏safe_scanf()不会“返回”一个值(它不是表达式,而是一个块语句)。

备注: 在宏内部,我声明了一个数组buffer,它是在进入块时创建的,然后在块时被销毁退出了。 buffer的范围仅限于宏的块。

答案 4 :(得分:0)

How to use sscanf correctly and safely

请注意,fnprintf并不是唯一的,大多数数组函数都有安全的变体。

答案 5 :(得分:0)

皱纹多了一点。 'n'通常是指snprintf中的第一个参数。现在,确实没有写入sscanf中的第一个字符串参数。但是,它被阅读。因此,以下可能是段错误:

char s[2];
s[0]='1'; s[1]='3';
int x;
sscanf(s, "%d", &x);

因为将一个char超出s可能会无意中从未定义的内存中读取(或从另一个变量继续整数)。所以,这样的事情会很有用:

 snscanf(s, 2, "%d", &x);
当然,

不是字符串,但它是一个字符数组。 snscanf中的'n'会阻止超越(读取)第一个(源字符串)参数,而不会与目标参数相关。

避免这种情况的方法是首先确保s在2个字符内以'\ 0'结尾。当然,你不能使用strlen。你需要strnlen,并测试它是否小于2.如果它是2,那么首先需要更多的复制工作。