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

时间:2022-09-11 18:43:14

I have snprintf and it can avoid a buffer overflow, but why there is no function called snscanf?

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

Code:

代码:

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

}

So, I think a snscanf is also needed. Why do we have only have snprintf?

所以,我认为snscanf也是必要的。为什么我们只有snprintf?

6 个解决方案

#1


10  

The controversial (and optional) Annex K to C11 adds a sscanf_s function which takes an additional argument of type rsize_t (also defined in Annex K) after the pointer argument, specifying the size of the pointed-to array. For better or worse, these functions are not widely supported. You can achieve the same results by putting the size in the conversion specifier, e.g.

C11中有争议的(可选的)附件K添加了sscanf_s函数,该函数在指针参数之后接受rsize_t类型的额外参数(也在附件K中定义),指定指向数组的大小。不管怎样,这些函数都没有得到广泛的支持。通过将大小放入转换说明符中,可以获得相同的结果。

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

but this is awkward and error-prone if the size of the destination object may vary at runtime (you would have to construct the conversion specifier programmatically with snprintf). Note that the field width in the conversion specifier is the maximum number of input characters to read, and sscanf also writes a terminating null byte for %s conversions, so the field width you pass must be strictly less than the size of the destination object.

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

#2


8  

There's no need for an snscanf() because there's no writing to the first buffer argument. The buffer length in snprintf() specifies the size of the buffer where the writing goes to:

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

char buffer[256];

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

The buffer in the corresponding position for sscanf() is a null-terminated string; there's no need for an explicit length as you aren't going to write to it (it's a const char * restrict buffer in C99 and C11).

sscanf()对应位置的缓冲区为空终止字符串;不需要显式的长度,因为不需要写入它(它是C99和C11中的const char * limit缓冲区)。

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

In the output, you are already expected to specify the length of the strings (though you're probably in the majority if you use %s rather than %99s or whatever is strictly appropriate):

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

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

It would be nice/useful if you could use %*s as you can with snprintf(), but you can't — in sscanf(), the * means 'do not assign scanned value', not the length. Note that you wouldn't write snscanf(src, sizeof(buf1), "%s", buf1), not least because you can have multiple %s conversion specifications in a single call. Writing snscanf(src, sizeof(buf1), sizeof(buf2), "%s %s", buf1, buf2) makes no sense, not least because it leaves an insoluble problem in parsing the varargs list. It would be nice to have a notation such as snscanf(src, "%@s %@s", sizeof(buf1), buf1, sizeof(buf2), buf2) to obviate the need to specify the field size (minus one) in the format string. Unfortunately, you can't do that with sscanf() et al now.

如果您可以像使用snprintf()那样使用%*s,这将是很好的/有用的,但是您不能—在sscanf()中,*表示“不分配扫描值”,而不是长度。注意,您不会编写snscanf(src、sizeof(buf1)、“%s”、buf1),尤其是因为您可以在一次调用中拥有多个%s转换规范。写snscanf(src, sizeof(buf1), sizeof(buf2),“%s %s”,buf1, buf2)没有意义,尤其是因为它在解析varargs列表时留下了一个不可解决的问题。最好有一个符号,比如snscanf(src,“%@s %@s %@s”,sizeof(buf1), buf1, sizeof(buf2), buf2),以避免在格式字符串中指定字段大小(- 1)。不幸的是,您现在不能使用sscanf()等方法。

Annex K of ISO/IEC 9899:2011 (previously TR24731) provides sscanf_s(), which does take lengths for character strings, and which might be used as:

ISO/IEC 9899:2011(之前的TR24731)的附件K提供了sscanf_s(),它确实对字符字符串进行了长度处理,可以用作:

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

(Thanks to R.. for reminding me of this theoretical option — theoretically because only Microsoft has implemented the 'safe' functions, and they did not implement them exactly as the standard requires.)

(由于R . .为了提醒我这一理论选择——理论上因为只有微软实现了“安全”功能,而且他们没有按照标准的要求实现它们。

Note that §K.3.3 Common definitions <stddef.h> says: '... The type is rsize_t which is the type size_t.385)' (and footnote 385 says: 'See the description of the RSIZE_MAX macro in <stdint.h>.' That means that in fact you can pass size_t without needing a cast — as long as the value passed is within the range defined by RSIZE_MAX in <stdint.h>. (The general intention is that RSIZE_MAX is a largish number but smaller than SIZE_MAX. For more details, read the 2011 standard, or get TR 24731 from the Open Standards web site.)

注意,§K.3.3 < stddef共同定义。h >说:“…类型是rsize_t,它是类型size_t.385)'(和footnote 385说:'参见 中的RSIZE_MAX宏的描述)。这意味着,实际上您可以不需要强制转换就传递size_t——只要传递的值在 中RSIZE_MAX定义的范围内。(总的意图是RSIZE_MAX是一个较大的数字,但小于SIZE_MAX。有关更多细节,请阅读2011标准,或从开放标准网站获取TR 24731。

#3


3  

In sscanf(s, format, ...), the the array of characters scanned is a const char *. There is no writing to s. The scanning stops when s[i] is NUL. Little need for an n parameter as an auxiliary limit to the scan.

在sscanf(s, format,…)中,扫描的字符数组为const char *。没有对s的写入。扫描在s[i]为NUL时停止。几乎不需要n个参数作为扫描的辅助限制。

In sprintf(s, format, ...), the array s is a destination. snprintf(s, n, format, ...) insures that data is not wriiten to s[n] and beyond.

在sprintf(s, format,…)中,数组s是一个目的地。snprintf(s, n, format,…)确保数据不会被写到s[n]或其他地方。


What would be useful is a flag extension to sscanf() conversion specifiers so a limit could easily specified at compile time. (It can be done in a cumbersome fashion today, below, with a dynamic format or with sscanf(src,"%4s",buf1).)

有用的是sscanf()转换说明符的标志扩展,这样在编译时可以很容易地指定限制。(现在可以用一种麻烦的方式完成,如下,使用动态格式或者使用sscanf(src,“%4s”,buf1))。

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

Here ! would tell sscanf() to read a size_t variable for the size limit the upcoming string. Maybe in C17?

这里!将告诉sscanf()读取size_t变量来限制即将到来的字符串的大小。也许在C17吗?


Cumbersome method that works today.

今天的方法很繁琐。

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

#4


1  

Why don't you try fgets() (with the standard input file stdin)?

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

fgets() lets you to specify the maximum size for your buffer.

fgets()允许您指定缓冲区的最大大小。

(In all what follows, I'll be using standard ISO C99 compatible syntax.)

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

Thus, you can write this code:

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

#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() reads at most MAXBUFF characters from stdin,
which is the standard input (that means: the keyboard).
The result is held in the array buffer.
If a '\n' character is found, the reading stops and '\n' is also held in buffer (as the last character). In addition, always a '\0' is added at the end of buffer, so enough storage is needed.
You can use a combination of fgets() followed by sscanf() in order to process the string:

fgets()从stdin中读取大多数MAXBUFF字符,stdin是标准输入(即:键盘)。结果被保存在数组缓冲区中。如果找到一个“\n”字符,那么读取停止和“\n”也被保存在缓冲区中(作为最后一个字符)。此外,在缓冲区的末尾添加了“\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 */

Thus, you have a "safe" scanf()-like method.

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

Note: This approach has a potencial problem. If fgets() reachs MAXBUFF characters before the end-of-line character '\n' is obtained, the rest of the input will not be discarded, and it will be taken as part of the next keyboard reading.
Hence, one has to add a flush mechanism, that actually is very simple:

注意:这种方法有潜在的问题。如果fgets()在获取行尾字符“\n”之前到达MAXBUFF字符,则不会丢弃其余输入,并将其作为下一个键盘读取的一部分。因此,我们必须增加一个冲洗机制,这其实很简单:

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

However: If you just add that last piece of code after the fgets() line,
the user will be forced two press ENTER two times each time (s)he enters less than MAXBUFF characters. Worst: this is the most typical situation!

但是:如果您只是在fgets()行之后添加最后一段代码,那么每次输入小于MAXBUFF字符时,用户将*两次按回车。最差:这是最典型的情况!

To fix this new problem, observe that an easy logical condition completeley equivalent to the fact that the character '\n' was not reached, is the following:

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

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

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

(Prove it!)

(证明它!)

Thus, we write:

因此,我们写:

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

A final touch is needed: since the array buffer could have garbadge,
it seems that some kind of initialization is needed.
However, let us observe that only the position [MAXBUFF - 1] has to be cleaned:

需要最后一点:由于数组缓冲区可以有garbadge,因此似乎需要某种初始化。然而,让我们注意到,只有[MAXBUFF - 1]需要清洗:

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

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

Finally, we can gather all that facts in a quick macro, like this program shows:

最后,我们可以在一个快速的宏中收集所有的事实,就像这个程序显示的:

#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;
}

It has been used the mechanism of variable number of parameters in a macro,
under the ISO C99 norms: Variadic macros
__VA_ARGS__ replaces the variable list of parameters.
(We need variable number of parameters in order to mimic the scanf()-like behaviour.)

它已经在ISO C99规范下使用了宏中可变参数数的机制:可变元宏__VA_ARGS__替换变量参数列表。(我们需要可变数量的参数来模拟scanf()类似的行为。)

Notes: The macro-body was enclosed inside a block with { }. This is not completely satisfactory, and it is easily improved, but it is part of another topic...
In particular, the macro safe_scanf() does not "return" a value (it is not an expression, but a block statement).

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

Remark: Inside the macro I have declared an array buffer which is created at the time of entering the block, and then is destroyed when the block is exited. The scope of buffer is limited to the block of the macro.

注意:在宏中,我声明了一个数组缓冲区,该缓冲区在进入块时创建,然后在块退出时销毁。缓冲区的范围仅限于宏的块。

#5


0  

How to use sscanf correctly and safely

如何正确、安全地使用sscanf

Note that fnprintf is not alone, and most array functions have a secure variation.

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

#6


0  

a little more wrinkles. the 'n' usually refers to the first argument in the snprintf. Now, it is true that the first string argument in sscanf is not written to. However, it is read. Thus, the following could segfault:

多一点皱纹。n通常指snprintf中的第一个参数。现在,sscanf中的第一个字符串参数是没有写入的。然而,阅读。因此,分段故障有以下几种:

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

because stepping one char beyond s could inadvertently step into reading from undefined memory (or continue the integer from another variable). so, something like this would be useful:

因为将一个字符置于s之上可能会无意中从未定义的内存中读取(或者从另一个变量中继续读取这个整数)。像这样的东西是有用的:

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

s is not a string, of course, but it is a character array. the 'n' in the snscanf would prevent overstepping (reading from) the first (source string) argument, and not be related to the destination argument.

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

the way to avoid this is to first make sure that s is terminated by a '\0' within 2 characters. you can't use strlen, of course. you need strnlen, and a test whether it is less than 2. if it is 2, then more copying effort is needed first.

避免这一点的方法是首先确保s在2个字符内被“\0”终止。当然,你不能使用strlen。你需要测试它是否小于2。如果是2,那么首先需要更多的复制工作。

#1


10  

The controversial (and optional) Annex K to C11 adds a sscanf_s function which takes an additional argument of type rsize_t (also defined in Annex K) after the pointer argument, specifying the size of the pointed-to array. For better or worse, these functions are not widely supported. You can achieve the same results by putting the size in the conversion specifier, e.g.

C11中有争议的(可选的)附件K添加了sscanf_s函数,该函数在指针参数之后接受rsize_t类型的额外参数(也在附件K中定义),指定指向数组的大小。不管怎样,这些函数都没有得到广泛的支持。通过将大小放入转换说明符中,可以获得相同的结果。

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

but this is awkward and error-prone if the size of the destination object may vary at runtime (you would have to construct the conversion specifier programmatically with snprintf). Note that the field width in the conversion specifier is the maximum number of input characters to read, and sscanf also writes a terminating null byte for %s conversions, so the field width you pass must be strictly less than the size of the destination object.

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

#2


8  

There's no need for an snscanf() because there's no writing to the first buffer argument. The buffer length in snprintf() specifies the size of the buffer where the writing goes to:

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

char buffer[256];

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

The buffer in the corresponding position for sscanf() is a null-terminated string; there's no need for an explicit length as you aren't going to write to it (it's a const char * restrict buffer in C99 and C11).

sscanf()对应位置的缓冲区为空终止字符串;不需要显式的长度,因为不需要写入它(它是C99和C11中的const char * limit缓冲区)。

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

In the output, you are already expected to specify the length of the strings (though you're probably in the majority if you use %s rather than %99s or whatever is strictly appropriate):

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

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

It would be nice/useful if you could use %*s as you can with snprintf(), but you can't — in sscanf(), the * means 'do not assign scanned value', not the length. Note that you wouldn't write snscanf(src, sizeof(buf1), "%s", buf1), not least because you can have multiple %s conversion specifications in a single call. Writing snscanf(src, sizeof(buf1), sizeof(buf2), "%s %s", buf1, buf2) makes no sense, not least because it leaves an insoluble problem in parsing the varargs list. It would be nice to have a notation such as snscanf(src, "%@s %@s", sizeof(buf1), buf1, sizeof(buf2), buf2) to obviate the need to specify the field size (minus one) in the format string. Unfortunately, you can't do that with sscanf() et al now.

如果您可以像使用snprintf()那样使用%*s,这将是很好的/有用的,但是您不能—在sscanf()中,*表示“不分配扫描值”,而不是长度。注意,您不会编写snscanf(src、sizeof(buf1)、“%s”、buf1),尤其是因为您可以在一次调用中拥有多个%s转换规范。写snscanf(src, sizeof(buf1), sizeof(buf2),“%s %s”,buf1, buf2)没有意义,尤其是因为它在解析varargs列表时留下了一个不可解决的问题。最好有一个符号,比如snscanf(src,“%@s %@s %@s”,sizeof(buf1), buf1, sizeof(buf2), buf2),以避免在格式字符串中指定字段大小(- 1)。不幸的是,您现在不能使用sscanf()等方法。

Annex K of ISO/IEC 9899:2011 (previously TR24731) provides sscanf_s(), which does take lengths for character strings, and which might be used as:

ISO/IEC 9899:2011(之前的TR24731)的附件K提供了sscanf_s(),它确实对字符字符串进行了长度处理,可以用作:

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

(Thanks to R.. for reminding me of this theoretical option — theoretically because only Microsoft has implemented the 'safe' functions, and they did not implement them exactly as the standard requires.)

(由于R . .为了提醒我这一理论选择——理论上因为只有微软实现了“安全”功能,而且他们没有按照标准的要求实现它们。

Note that §K.3.3 Common definitions <stddef.h> says: '... The type is rsize_t which is the type size_t.385)' (and footnote 385 says: 'See the description of the RSIZE_MAX macro in <stdint.h>.' That means that in fact you can pass size_t without needing a cast — as long as the value passed is within the range defined by RSIZE_MAX in <stdint.h>. (The general intention is that RSIZE_MAX is a largish number but smaller than SIZE_MAX. For more details, read the 2011 standard, or get TR 24731 from the Open Standards web site.)

注意,§K.3.3 < stddef共同定义。h >说:“…类型是rsize_t,它是类型size_t.385)'(和footnote 385说:'参见 中的RSIZE_MAX宏的描述)。这意味着,实际上您可以不需要强制转换就传递size_t——只要传递的值在 中RSIZE_MAX定义的范围内。(总的意图是RSIZE_MAX是一个较大的数字,但小于SIZE_MAX。有关更多细节,请阅读2011标准,或从开放标准网站获取TR 24731。

#3


3  

In sscanf(s, format, ...), the the array of characters scanned is a const char *. There is no writing to s. The scanning stops when s[i] is NUL. Little need for an n parameter as an auxiliary limit to the scan.

在sscanf(s, format,…)中,扫描的字符数组为const char *。没有对s的写入。扫描在s[i]为NUL时停止。几乎不需要n个参数作为扫描的辅助限制。

In sprintf(s, format, ...), the array s is a destination. snprintf(s, n, format, ...) insures that data is not wriiten to s[n] and beyond.

在sprintf(s, format,…)中,数组s是一个目的地。snprintf(s, n, format,…)确保数据不会被写到s[n]或其他地方。


What would be useful is a flag extension to sscanf() conversion specifiers so a limit could easily specified at compile time. (It can be done in a cumbersome fashion today, below, with a dynamic format or with sscanf(src,"%4s",buf1).)

有用的是sscanf()转换说明符的标志扩展,这样在编译时可以很容易地指定限制。(现在可以用一种麻烦的方式完成,如下,使用动态格式或者使用sscanf(src,“%4s”,buf1))。

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

Here ! would tell sscanf() to read a size_t variable for the size limit the upcoming string. Maybe in C17?

这里!将告诉sscanf()读取size_t变量来限制即将到来的字符串的大小。也许在C17吗?


Cumbersome method that works today.

今天的方法很繁琐。

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

#4


1  

Why don't you try fgets() (with the standard input file stdin)?

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

fgets() lets you to specify the maximum size for your buffer.

fgets()允许您指定缓冲区的最大大小。

(In all what follows, I'll be using standard ISO C99 compatible syntax.)

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

Thus, you can write this code:

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

#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() reads at most MAXBUFF characters from stdin,
which is the standard input (that means: the keyboard).
The result is held in the array buffer.
If a '\n' character is found, the reading stops and '\n' is also held in buffer (as the last character). In addition, always a '\0' is added at the end of buffer, so enough storage is needed.
You can use a combination of fgets() followed by sscanf() in order to process the string:

fgets()从stdin中读取大多数MAXBUFF字符,stdin是标准输入(即:键盘)。结果被保存在数组缓冲区中。如果找到一个“\n”字符,那么读取停止和“\n”也被保存在缓冲区中(作为最后一个字符)。此外,在缓冲区的末尾添加了“\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 */

Thus, you have a "safe" scanf()-like method.

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

Note: This approach has a potencial problem. If fgets() reachs MAXBUFF characters before the end-of-line character '\n' is obtained, the rest of the input will not be discarded, and it will be taken as part of the next keyboard reading.
Hence, one has to add a flush mechanism, that actually is very simple:

注意:这种方法有潜在的问题。如果fgets()在获取行尾字符“\n”之前到达MAXBUFF字符,则不会丢弃其余输入,并将其作为下一个键盘读取的一部分。因此,我们必须增加一个冲洗机制,这其实很简单:

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

However: If you just add that last piece of code after the fgets() line,
the user will be forced two press ENTER two times each time (s)he enters less than MAXBUFF characters. Worst: this is the most typical situation!

但是:如果您只是在fgets()行之后添加最后一段代码,那么每次输入小于MAXBUFF字符时,用户将*两次按回车。最差:这是最典型的情况!

To fix this new problem, observe that an easy logical condition completeley equivalent to the fact that the character '\n' was not reached, is the following:

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

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

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

(Prove it!)

(证明它!)

Thus, we write:

因此,我们写:

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

A final touch is needed: since the array buffer could have garbadge,
it seems that some kind of initialization is needed.
However, let us observe that only the position [MAXBUFF - 1] has to be cleaned:

需要最后一点:由于数组缓冲区可以有garbadge,因此似乎需要某种初始化。然而,让我们注意到,只有[MAXBUFF - 1]需要清洗:

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

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

Finally, we can gather all that facts in a quick macro, like this program shows:

最后,我们可以在一个快速的宏中收集所有的事实,就像这个程序显示的:

#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;
}

It has been used the mechanism of variable number of parameters in a macro,
under the ISO C99 norms: Variadic macros
__VA_ARGS__ replaces the variable list of parameters.
(We need variable number of parameters in order to mimic the scanf()-like behaviour.)

它已经在ISO C99规范下使用了宏中可变参数数的机制:可变元宏__VA_ARGS__替换变量参数列表。(我们需要可变数量的参数来模拟scanf()类似的行为。)

Notes: The macro-body was enclosed inside a block with { }. This is not completely satisfactory, and it is easily improved, but it is part of another topic...
In particular, the macro safe_scanf() does not "return" a value (it is not an expression, but a block statement).

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

Remark: Inside the macro I have declared an array buffer which is created at the time of entering the block, and then is destroyed when the block is exited. The scope of buffer is limited to the block of the macro.

注意:在宏中,我声明了一个数组缓冲区,该缓冲区在进入块时创建,然后在块退出时销毁。缓冲区的范围仅限于宏的块。

#5


0  

How to use sscanf correctly and safely

如何正确、安全地使用sscanf

Note that fnprintf is not alone, and most array functions have a secure variation.

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

#6


0  

a little more wrinkles. the 'n' usually refers to the first argument in the snprintf. Now, it is true that the first string argument in sscanf is not written to. However, it is read. Thus, the following could segfault:

多一点皱纹。n通常指snprintf中的第一个参数。现在,sscanf中的第一个字符串参数是没有写入的。然而,阅读。因此,分段故障有以下几种:

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

because stepping one char beyond s could inadvertently step into reading from undefined memory (or continue the integer from another variable). so, something like this would be useful:

因为将一个字符置于s之上可能会无意中从未定义的内存中读取(或者从另一个变量中继续读取这个整数)。像这样的东西是有用的:

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

s is not a string, of course, but it is a character array. the 'n' in the snscanf would prevent overstepping (reading from) the first (source string) argument, and not be related to the destination argument.

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

the way to avoid this is to first make sure that s is terminated by a '\0' within 2 characters. you can't use strlen, of course. you need strnlen, and a test whether it is less than 2. if it is 2, then more copying effort is needed first.

避免这一点的方法是首先确保s在2个字符内被“\0”终止。当然,你不能使用strlen。你需要测试它是否小于2。如果是2,那么首先需要更多的复制工作。