20

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

代码:

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

}

所以,我认为 asnscanf也是需要的。为什么我们只有拥有snprintf

4

6 回答 6

11

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

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

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

于 2013-08-21T22:49:07.513 回答
10

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

char buffer[256];

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

for对应位置的缓冲区sscanf()是一个以null结尾的字符串;不需要明确的长度,因为您不会写入它(它是const char * restrict bufferC99 和 C11 中的 a)。

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

在输出中,您已经希望指定字符串的长度(尽管如果您使用%s而不是%99s或任何严格合适的方法,您可能是大多数人):

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)没有意义,尤其是因为它在解析可变参数列表时留下了一个无法解决的问题。最好有一个符号snscanf(src, "%@s %@s", sizeof(buf1), buf1, sizeof(buf2), buf2)来避免在格式字符串中指定字段大小(减一)的需要。不幸的是,你现在不能用sscanf()et al 做到这一点。

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

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

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

请注意,§K.3.3通用定义<stddef.h>说:'...类型是rsize_twhich is the type size_t385) ' (并且脚注 385 说:'请参阅 RSIZE_MAX 宏中的描述<stdint.h>.' 这意味着实际上您可以在不需要强制转换的情况下通过size_t- 只要传递的值在 in 定义的范围内RSIZE_MAX<stdint.h>(一般意图是RSIZE_MAX一个较大的数字,但小于。有关更多详细信息,请阅读 2011 标准,或从开放标准网站SIZE_MAX获取 TR 24731。 )

于 2013-08-21T22:45:44.233 回答
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);
于 2013-08-21T22:43:30.080 回答
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()最多从 MAXBUFF 读取字符stdin
这是标准输入(即:键盘)。
结果保存在数组中buffer
如果找到 '\n' 字符,则停止读取并且 '\n' 也被保留buffer(作为最后一个字符)。此外,总是在 '\0' 的末尾添加一个buffer,因此需要足够的存储空间。
您可以使用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字符,则不会丢弃其余输入,并将其作为下一次键盘读取的一部分。
因此,必须添加一个刷新机制,这实际上非常简单:

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

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

要解决这个新问题,请注意一个简单的逻辑条件完全等同于字符 '\n' was not reached,如下所示:

(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')
       ;

需要最后一点:由于数组缓冲区可能有垃圾,
似乎需要某种初始化。
但是,让我们观察到只[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规范 下,它已被用于宏中参数数量可变的机制:可变参数替换参数的可变列表。 (我们需要可变数量的参数来模拟类似的行为。)

__VA_ARGS__
scanf()

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

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

于 2013-08-22T03:58:54.387 回答
1

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

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

因为在 s 之外步进一个字符可能会无意中从未定义的内存中读取(或继续从另一个变量中读取整数)。所以,这样的事情会很有用:

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

当然,s 不是字符串,而是字符数组。snscanf 中的“n”将防止越过(读取)第一个(源字符串)参数,并且与目标参数无关。

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

于 2015-02-10T23:59:22.860 回答
0

如何正确安全地使用sscanf

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

于 2013-08-21T22:29:03.893 回答