使用scanf函数获得最大字符串长度 -> ANSI C

6

我有:

#define MAX_STR_LEN 100

我希望你能帮我将内容转化为scanf模式,这样我就可以控制字符串的长度:

scanf("%100[^\n]s",sometext)

我尝试过:

scanf("%MAX_STR_LEN[^\n]s",sometext)
scanf("%"MAX_STR_LEN"[^\n]s",sometext)
scanf("%",MAX_STR_LEN,"[^\n]s",sometext)

但是它没有起作用。我只是想避免缓冲区溢出,因为"sometext"是使用malloc(MAX_STR_LEN)分配的...

有什么想法吗?


在这里讨论了如何正确和安全地使用sscanf:https://dev59.com/questions/YmDVa4cB1Zd3GeqPhuQl。 - meaning-matters
你如何在格式化字符串中使用 MAX_STR_LEN - amulous
@amulous,由于它遵循严格的ANSI C标准,我无法真正访问字符串变量。我需要这样做:char *somestring再加上malloc... - tomdavies
可能已经太晚了,但我写了这个函数来解决那个问题。https://github.com/tsw1985/Gscanf - TSW1985
1
除了这里的答案,我想指出如果你使用"%100s",那么分配需要101个字节,所以在你的例子中,你需要malloc(MAX_STR_LEN+1)或者是"%99s" - Tim Sylvester
5个回答

10

我对这些解决方案都不满意,所以我进行了进一步的研究,并发现了GNU GCC宏字符串化

可以这样使用:

#define XSTR(A) STR(A)
#define STR(A) #A
#define MAX_STR_LEN 100
scanf("%"XSTR(MAX_STR_LEN)"[^\n]s", sometext)

或许VS2010提供了类似的东西?


5
这是在ANSI C标准中的内容,它应该可以与VS 2010很好地配合使用(但考虑到VS的特殊性,我不太确定)。 - michaelb958--GoFundMonica
2
这是正确的答案。对于那些说“不要使用scanf()”的人来说,这应该足以避免OP所描述的缓冲区溢出问题。 - jimis
不幸的是,这会破坏大多数IDE和编译时格式字符串检查。 - Tim Sylvester

7

我只是想避免缓冲区溢出

那么就完全不要使用scanf()

如果你正在扫描文本行,也不要使用#define MAX_STR。你可以使用 <limits.h> 中的 LINE_MAX(如果你的目标系统是 POSIX 兼容的):

char buf[LINE_MAX];
fgets(buf, sizeof(buf), stdin);

应该可以解决问题。

4
几乎每个人都会说,最好使用 fgets(..., stdin) 来处理这个问题。
在下面的链接中,我提出了一种安全和正确的技术,可以通过一个可靠的来替换scanf()一个可以安全替换scanf()的宏 我提出的宏(适用于兼容的C99编译器)是 safe_scanf(),如下所示:
#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;
}  

您需要根据自己的需求调整MAXBUFF的值...尽管宏safe_scanf()相当可靠,但使用宏的方法存在一些弱点:参数缺少类型检查、缺少返回值(与返回有价值的错误检查信息的“真正”scanf()函数几乎没有区别)等。所有这些问题都有解决办法,但这是另一个话题的一部分...也许最精确的解决方案是定义一个具有可变数量参数的函数my_scanf(),通过调用联合fgets()vsscanf()stdarg.h库来实现。以下是代码:
#include <stdio.h>
#include <stdarg.h>

int my_scanf(const char* fmt, const unsigned int maxbuff, ...) {
    va_list ptr;
    int ret;

    if (maxbuff <= 0)
       return EOF; /* Bad size for buffer[] */

    char buffer[maxbuff+1];
    buffer[maxbuff-1] = '\0';  /* Quick buffer cleaning... */

    if (fgets(buffer, maxbuff+1, stdin) == NULL)
       return EOF; /* Error detected */
    else {
        if ((buffer[maxbuff-1] != '\n') && (buffer[maxbuff-1] != '\0'))
            /* Condition logically equivalent to:
                   fgets() has not reached an '\n'
            */
            while (getchar() != '\n')
               ; /* "Flushing" stdin... */

        va_start(ptr, maxbuff);
        ret = vsscanf(buffer, fmt, ptr);
        va_end(ptr);
        return ret;
    }    
}

#define MAXBUFF 20
int main(void) {
   int x; 
   float z;
   int scanf_ret = my_scanf("%d %g", MAXBUFF, &x, &z);
   printf("\nTest:\n x == %d\n z == %g\n scanfret == %d", x, z, scanf_ret);
   getchar();   
   return 0;   
}

函数my_scanf()的原型如下:

int my_scanf(const char* fmt, const int maxbuff, ...);

它接受一个格式字符串fmt,其行为与任何其他类似于scanf()的函数相同。
第二个参数是从标准输入(键盘)实际接受的字符的最大数量。
返回值是一个int,如果maxbuff没有意义或出现输入错误,则为EOF。如果返回非负值,则与标准函数sscanf()vsscanf()返回的值相同。
在函数内部,maxbuff增加1,因为fgets()为额外的'\0'字符腾出了一些空间。
非正数值的maxbuff会立即被丢弃。
fgets()将从stdin(键盘)读取一个字符串,最多包含maxbuff个字符,包括'\n'。
如果用户输入了一个非常长的字符串,则它将被截断,并且需要某种“刷新”机制以丢弃所有字符到下一个'\n'(ENTER)。否则,下一个键盘读取可能具有旧字符,这是不可取的。
“刷新”的条件是fgets()在读取stdin后未达到'\n'。
只有当buffer[maxbuff - 1]不等于'\0'或'\n'时,才会出现这种情况。
检查一下!
最后,使用stdarg.h的适当组合和函数vsscanf()来处理可变参数列表。

0

推荐使用 fgets(buffer, sizeof(buffer), stdin) 方法。

如果你仍想使用 scanf(),可以在运行时创建其格式。

#define MAX_STR_LEN 100
char format[2 + sizeof(size_t)*3 + 4 + 1];  // Ugly magic #
sprintf(format, " %%%zu[^\n]", (size_t) MAX_STR_LEN);
scanf(format, sometext);

或重新定义MAX_STR_LEN为字符串

#define MAX_STR_LEN "100"
scanf(" %" MAX_STR_LEN "[^\n]", sometext);

仍然推荐使用fgets()
注意,fgets()会将前导空格和尾随的\n放入您的缓冲区,而"% [^\n]"则不会。
顺便说一句:你格式中的尾随s可能不是你想要的。


-3

怎么样?

scanf("%.*[^\n]s", MAX_STR_LEN, sometext)

请,请,拜托了,不行! - user529758
3
建议您重新阅读 scanf() 中的 * 用法。我相信您会想要修改您的帖子。在 printf() 的用法中,* 会读取一个整数作为大小,但在 scanf() 中不是这样。* 的含义是“可选的起始星号表示数据将从流中读取,但将被忽略”。这是一个非常不同的功能。 - chux - Reinstate Monica

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接