我知道在C中你可以声明一个字符串和下面的字符数,
char mystring[50];
'50'是字符数。
但是,如果用户要输入字符串的内容(通过scanf(“%s”,mystring);),那么正确的过程是什么?我把它留下来,
char mystring[0];
将其保留为“0”,因为我不知道用户将输入多少个字符?
或者我这样做,
char mystring[400];
最多可输入400个字符供用户输入?
答案 0 :(得分:6)
你已经遇到了scanf()和%s的确切问题 - 当你不知道有多少输入时会发生什么?
如果您尝试运行char mystring[0];
,您的程序将编译得很好。但你总是会发生段错误。你正在创建一个大小为0的数组,所以当你尝试将放入那个数组时,你会立即超出你的字符串的范围(因为没有分配内存) - 这是一个段错误。
所以,第1点:你应该总是为你的字符串分配一个大小。我可以想到很少有情况(好的,没有),你想说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次)。
答案 1 :(得分:2)
用户将始终能够输入更多字符,从而溢出缓冲区(安全漏洞的常见来源)。但是,您可以为scanf指定“字段宽度”,如下所示:
scanf("%50s", mystring);
在这种情况下,您的缓冲区应为51个字符,以考虑50个字符的字段加上空终止符。或者将缓冲区设为50个字符,并告诉scanf 49是宽度。
答案 2 :(得分:2)
有一个名为ggets()的函数,它不是标准C库的一部分。 这是一个相当简单的功能。它使用malloc()初始化char数组。然后它一次从stdin读取一个字符的字符。它会跟踪读取的字符数,并在空间不足时使用realloc()扩展char数组。
可在此处找到:http://cbfalconer.home.att.net/download/index.htm
我建议你阅读代码并重新实现自己。
答案 3 :(得分: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 */
我测试了它,它总会给你你想要的东西。
答案 4 :(得分:0)
C中的常用做法是使用类似GNU readline或NetBSD editline, aka libedit.的内容(相同的API,不同的实现和软件许可。)
对于更简单或家庭作业的程序,理论上你可以给scanf一个字段宽度,但更常见的做法是fgets()
到固定宽度的数组,然后运行sscanf()
。这样您就可以控制读取的行数。
答案 5 :(得分:0)
例如,如果用户输入他们的名字,那么你并不总是安全地将'mystring'的大小最大化为35个字符,因为有些人的名字很长。您不希望达到用户无法完整输入您请求的信息的情况。正确的方法是使用一个非常大的临时缓冲区来覆盖用户的所有可能输入。一旦用户输入信息并将其存储到缓冲区中,您就可以将字符从缓冲区传输到mystring,同时切断缓冲区末尾的所有额外空间。您将能够准确地告诉“mystring”所需的大小,并且可以为其量化大量空间并丢弃缓冲区。这样你就不会使用一个字符串为程序的其余部分使用更多的内存......你只会使用一个字符串,其中包含你需要的内存量。