我知道在C,可申报一个字符串,像下面的字符数,用C确定字符的字符串的长度 - 如果用户输入字符串的内容
char mystring[50];
与“50”是字符数。
但是,如果用户要输入字符串的内容(通过scanf(“%s”,mystring);),什么是正确的过程?我是否将其保留为:
char mystring[0];
将它留为'0',因为我不知道用户将输入多少个字符?
还是我做的,
char mystring[400];
放弃以400个字符的用户输入?
我知道在C,可申报一个字符串,像下面的字符数,用C确定字符的字符串的长度 - 如果用户输入字符串的内容
char mystring[50];
与“50”是字符数。
但是,如果用户要输入字符串的内容(通过scanf(“%s”,mystring);),什么是正确的过程?我是否将其保留为:
char mystring[0];
将它留为'0',因为我不知道用户将输入多少个字符?
还是我做的,
char mystring[400];
放弃以400个字符的用户输入?
你遇到了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次)。
用户将始终能够输入更多字符,从而使您的缓冲区溢出(一种常见的安全漏洞来源)。你可以,但是,指定“字段宽度”来scanf函数,像这样:
scanf("%50s", mystring);
在这种情况下,您的缓冲区应该是51个字符,以占50字符字段加空终止。或者让你的缓冲区50个字符,并告诉scanf 49是宽度。
但是当声明字符串时,我应该指定'0'还是一些大数字? – HollerTrain 2009-11-29 03:49:22
在本例中,您应该至少指定51。 (空终止符的长度+1。) – Thanatos 2009-11-29 03:50:30
好的。所以当声明字符串不正确的编码时,将它列为'0'?我的问题是我不知道有多少用户会输入,但同时想要学习正确的方法... – HollerTrain 2009-11-29 03:51:58
有一个名为ggets()的函数,它不是标准C库的一部分。 这是一个相当简单的功能。它使用malloc()初始化一个char数组。然后它每次从stdin中读取一个字符的字符。它跟踪有多少个字符被读取,并在空间不足时使用realloc()扩展字符数组。
它可以在这里找到:http://cbfalconer.home.att.net/download/index.htm
我建议你阅读代码,并重新实现自己。
C中的通常的做法是使用类似GNU readline或许NetBSD editline, aka libedit.(同样的API,不同的实现和软件许可。)
对于简单或作业程序,理论上你可以给一个字段宽度SCANF ,但更常见的做法是将fgets()
固定宽度的阵列,然后在其上运行sscanf()
。这样你就可以控制读取的行数。
作为一个例子,如果用户输入他们的名字,那么你并不总是安全地将'mystring'的大小最大化为35个字符,因为有些人有很长的名字。您不希望触及用户无法完整输入您请求的信息的情况。正确的做法是制作一个尺寸非常大的临时缓冲区,以覆盖用户所有可能的输入。一旦用户输入信息并将其存储到缓冲区中,您就可以将缓冲区中的字符传送到mystring,同时切断缓冲区末尾的所有额外空间。你将能够精确地告诉'mystring'所需的大小,并且你可以为它分配足够的空间并丢弃缓冲区。这样你就不会在程序的其余部分使用更多内存的字符串......你只会使用一个带有你需要的内存量的字符串。
您仍然需要进行一些检查,以确保用户输入的内容不会大于在极少数情况下分配的缓冲区或者某人正在尝试利用您的程序时分配的缓冲区。 – 2009-11-29 04:16:33
这是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调用中用infile替换stdin。 – 2009-11-29 05:15:37
应该加上%s读单词,而不是整个字符串。因为scanf格式字符串使用空格和换行符作为分隔符。在这种情况下,请使用%c来代替(使用字段宽度),或者像上面提到的那样使用fgets。在字段宽度为%c的情况下,请记住将整个缓冲区字符串初始化为零。 – 2009-11-29 04:55:09
程序不会总是出现段错误。事实上,可能不是大部分时间。你的程序可能会被无声地破坏。 C不可爱吗? :-) – 2009-11-29 17:57:43