在C语言中确定字符字符串的长度 - 如果用户输入字符串的内容

5

我知道在C语言中,你可以像下面这样声明一个字符串和字符的数量:

char mystring[50];

数字'50'代表字符数。

然而,如果用户将通过scanf("%s", mystring);输入字符串的内容,那么正确的程序是什么呢?我应该保留原样吗,

char mystring[0];

由于我不知道用户会输入多少字符,因此将其保留为“0”?

还是我应该这样做,

char mystring[400];

让用户输入多达400个字符?
6个回答

6

你遇到了scanf()和%s的确切问题——当你不知道输入有多少时会发生什么?

如果你尝试运行char mystring[0];,你的程序将编译得很好。但你总是会出现段错误。你创建了一个大小为0的数组,所以当你试图把东西放入那个数组中时,你会立即超出字符串的边界(因为没有分配任何内存)——这就是段错误。

因此,第一点:你应该始终为你的字符串分配一个大小。我想不出有什么情况(好吧,没有)需要使用char mystring[0]而不是char *mystring

接下来,在使用scanf时,你永远不要使用"%s"说明符——因为这不会对字符串的大小进行任何边界检查。所以即使你有:

char mystring[512];
scanf("%s", mystring);

如果用户输入超过511个字符(因为第512个是\0),您将超出数组的范围。解决方法如下:

scanf("%511s", mystring);

这就是说,C语言没有自动调整字符串大小的功能,如果输入超出了预期,你必须手动处理。

解决这个问题的一种方法是使用 fgets() 函数。

你可以这样写:

while (fgets(mystring, 512, stdin))
{
   /* process input */
}

你可以使用sscanf()来解析mystring字符串。尝试使用上面的代码,用一个长度为5的字符串。在读取了4个字符之后,该代码再次循环以检索其余的输入。"处理"可能包括重新分配字符串大小并追加来自fgets()的最新输入的代码。以上代码不完美 - 它会让你的程序循环并处理任何无限长度的字符串,所以你可能希望对其进行一些内部硬性限制(例如,最多循环10次)。

应该补充说明,%s 读取的是单词,而不是整个字符串。因为 scanf 格式字符串使用空格和换行符作为分隔符。在这种情况下,应该使用带有字段宽度的 %c,或者如你所提到的那样使用 fgets。对于带有字段宽度的 %c,记得将整个缓冲区字符串初始化为零。 - Mads Elvheim
程序不会总是崩溃。实际上,大多数情况下可能都不会。你的程序很可能只是默默地出了问题。C语言真可爱,不是吗? :-) - Tommy McGuire

2
用户总是可以输入更多的字符,从而溢出您的缓冲区(这是安全漏洞的常见来源)。但是,您可以使用如下所示的方式向scanf指定“字段宽度”:
scanf("%50s", mystring);

在这种情况下,您的缓冲区应该是51个字符,以考虑到50个字符字段加上空终止符。或者将您的缓冲区设置为50个字符,并告诉scanf 49是宽度。

但是在声明字符串时,我应该指定'0'还是一些较大的数字? - Zach Smith
1
在这个例子中,你应该至少指定51(长度+1为了空终止符)。 - Thanatos
好的。所以在声明字符串时,仅将其列为“0”是否不是正确的编码方式?我的问题是我不知道用户会输入多少个,但同时想学习正确的方法... - Zach Smith
1
Thanatos是正确的:您不应将0指定为char数组大小,因为您确实需要字符串的空间(而C字符串不是动态大小的)。 - John Zwinck
2
当你说你“不知道”会输入多少个字符时,真的没有最大限制吗?也许你可以更详细地解释一下你的程序需求。 - John Zwinck

2
有一个名为ggets()的函数,它不是标准C库的一部分。 这是一个相当简单的函数。它使用malloc()初始化char数组。然后它逐个字符从stdin读取字符。它跟踪读取了多少个字符,并在空间不足时使用realloc()扩展char数组。
您可以在此处找到它:http://cbfalconer.home.att.net/download/index.htm 我建议您阅读代码并重新实现它。

1
这是cbfalconer的代码(http://cbfalconer.home.att.net/download/index.htm),经过一些小修改后编译成一个文件:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "ggets.h"

#define INITSIZE   112  /* power of 2 minus 16, helps malloc */
#define DELTASIZE (INITSIZE + 16)

enum {OK = 0, NOMEM};

int fggets(char* *ln, FILE *f)
{
   int     cursize, ch, ix;
   char   *buffer, *temp;

   *ln = NULL; /* default */
   if (NULL == (buffer = malloc(INITSIZE))) return NOMEM;
   cursize = INITSIZE;

   ix = 0;
   while ((EOF != (ch = getc(f))) && ('\n' != ch)) {
      if (ix >= (cursize - 1)) { /* extend buffer */
         cursize += DELTASIZE;
         if (NULL == (temp = realloc(buffer, (size_t)cursize))) {
            /* ran out of memory, return partial line */
            buffer[ix] = '\0';
            *ln = buffer;
            return NOMEM;
         }
         buffer = temp;
      }
      buffer[ix++] = ch;
   }
   if ((EOF == ch) && (0 == ix)) {
      free(buffer);
      return EOF;
   }

   buffer[ix] = '\0';
   if (NULL == (temp = realloc(buffer, (size_t)ix + 1))) {
      *ln = buffer;  /* without reducing it */
   }
   else *ln = temp;
   return OK;
} /* fggets */
/* End of ggets.c */

int main(int argc, char **argv)
{
   FILE *infile;
   char *line;
   int   cnt;

   //if (argc == 2)
      //if ((infile = fopen(argv[1], "r"))) {
         cnt = 0;
         while (0 == fggets(&line, stdin)) {
            fprintf(stderr, "%4d %4d\n", ++cnt, (int)strlen(line));
            (void)puts(line);
            free(line);
         }
         return 0;
      //}
   //(void)puts("Usage: tggets filetodisplay");
   //return EXIT_FAILURE;
} /* main */
/* END file tggets.c */

我测试过了,它总是会给你想要的结果。


基本上,要获取他的原始代码,您需要取消注释并将fggets调用中的stdin替换为infile。 - Brian T Hannan

0
在C语言中通常的做法是使用类似于GNU readline或者NetBSD editline,也称为libedit.的内容(相同的API,不同的实现和软件许可证)。
对于更简单或作业程序,理论上可以给scanf一个字段宽度,但更常见的做法是使用fgets()到固定宽度数组,然后在该数组上运行sscanf()。这样你就可以控制读入的行数了。

0
例如,如果用户正在输入他们的名字,那么您不能总是将“mystring”的大小最大化为35个字符,因为有些人的名字非常长。您不希望出现用户无法完整输入所请求信息的情况。正确的方法是使用一个临时缓冲区,其大小非常大,可以覆盖用户可能输入的所有内容。一旦用户输入信息并将其存储到缓冲区中,您就可以将字符从缓冲区传输到mystring中,并在缓冲区末尾削减所有额外的空间。您将能够准确确定“mystring”所需的大小,并为其分配恰好那么多的空间,并且丢弃缓冲区。这样,您将不会在程序的其余部分中使用更多内存的字符串...您只会使用所需内存量的字符串。

在非常罕见的情况下或有人试图利用您的程序时,您仍需要进行某种检查以确保用户输入的内容不大于分配的缓冲区。 - Brian T Hannan

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