2011-02-02 184 views
7

我有这样的片断代码:字符串输入和输出用C

char* receiveInput(){ 
    char *s; 
    scanf("%s",s); 

    return s; 
} 

int main() 
{ 
    char *str = receiveInput(); 
    int length = strlen(str); 

    printf("Your string is %s, length is %d\n", str, length); 

    return 0; 
} 

我收到这样的输出:

Your string is hellàÿ", length is 11 

我输入的是:

helloworld! 

有人可以解释为什么,以及为什么这种编码风格不好,请提前致谢

回答

10

scanf不会为您分配内存。

您需要为传递给scanf的变量分配内存。

你可以这样做:

char* receiveInput(){ 
    char *s = (char*) malloc(100); 
    scanf("%s",s); 
    return s; 
} 

但警告:

  1. 调用receiveInput将返回的内存的所有权的功能:你会在打印后必须free(str)它在main。 (以这种方式剥夺所有权通常不被认为是一种好的做法)。

    一个简单的解决方法是获取分配的内存作为参数。

  2. 如果输入字符串长于99(在我的情况下),您的程序将遭受缓冲区溢出(这是它已经发生的情况)。

    一个简单的办法是通过对scanf您的缓冲区长度:

    scanf("%99s",s); 
    

一个固定的代码可能是这样的:

// s must be of at least 100 chars!!! 
char* receiveInput(char *s){ 
    scanf("%99s",s); 
    return s; 
} 
int main() 
{ 
    char str[100]; 
    receiveInput(str); 
    int length = strlen(str); 

    printf("Your string is %s, length is %d\n", str, length); 

    return 0; 
} 
+1

还需要stdlib include for malloc! – 2011-02-02 10:01:54

+2

@fehergeri:是的,stdio使用`scanf`。我猜标题已被删除的目的? – peoro 2011-02-02 10:02:45

+0

我宁愿使它成为'receiveInput(char * s,size_t len)`,但是你需要跳过构建格式字符串的一些箍。 – 2011-02-02 10:05:29

2

你必须先分配内存您的receiveInput()方法中的对象。如:

s = (char *)calloc(50, sizeof(char)); 
20

几个问题已经解决了你做了什么错误,以及如何解决它,但你也说(重点煤矿):

有人可以解释为什么,为什么编码风格不好

我觉得scanf是一种很糟糕的阅读输入法。这与printf不一致,因此很容易忘记检查错误,使其很难从错误中恢复,并且与普通(且更容易正确地执行)读取操作不兼容(如fgets和company)。

首先,请注意"%s"格式将只读,直到它看到空格为止。为什么是空格?为什么"%s"会打印出一个完整的字符串,但在这样有限的容量中读取字符串?

如果您想要阅读整行内容,正如您可能经常想要做的那样,scanf提供了......以及"%[^\n]"。什么?那是什么?这是什么时候变成Perl的?

但真正的问题是,无论这些都是安全的。他们都自由溢出,没有边界检查。想边界检查?好吧,你明白了:"%10s"(和"%10[^\n]"开始变得更糟)。这将只读取9个字符,并自动添加一个终止nul字符。所以这很好......因为当我们的数组大小从不需要改变

如果我们希望我们的数组的大小作为参数传递给scanfprintf可以这样做:

char string[] = "Hello, world!"; 
printf("%.*s\n", sizeof string, string); // prints whole message; 
printf("%.*s\n", 6, string); // prints just "Hello," 

想做同样的事情scanf?具体方法如下:

static char tmp[/*bit twiddling to get the log10 of SIZE_MAX plus a few*/]; 
// if we did the math right we shouldn't need to use snprintf 
snprintf(tmp, sizeof tmp, "%%%us", bufsize); 
scanf(tmp, buffer); 

这是正确的 - scanf不支持"%.*s"可变精度printf做,这样做动态范围与scanf检查我们必须构建我们自己的格式字符串在临时缓冲区。这是各种坏了,尽管它实际上是在这里安全的它看起来像一个非常糟糕的主意,任何人都只是删除英寸

同时,让我们来看看另一个世界。我们来看看fgets的世界。以下是我们在一条线上的数据与fgets阅读:

fgets(buffer, bufsize, stdin); 

无限少头痛,无浪费的处理器时间转换为整数精确到一个字符串,将仅由库重新解析回一个整数,所有的相关元素正坐在的一行上,让我们看看它们是如何一起工作的。

当然,这可能不读取整行。如果该行比bufsize - 1个字符短,它将只读取整行。下面是我们如何可以阅读一整行:

char *readline(FILE *file) 
{ 
    size_t size = 80; // start off small 
    size_t curr = 0; 
    char *buffer = malloc(size); 
    while(fgets(buffer + curr, size - curr, file)) 
     { 
     if(strchr(buffer + curr, '\n')) return buffer; // success 
     curr = size - 1; 
     size *= 2; 
     char *tmp = realloc(buffer, size); 
     if(tmp == NULL) /* handle error */; 
     buffer = tmp; 
     } 
    /* handle error */; 
} 

curr变量是阻止我们重新检查我们已经读出的数据进行优化,并且是不必要的(虽然有用,因为我们读更多的数据)。如果您愿意,我们甚至可以使用返回值strchr去除结尾"\n"字符。

还要注意size_t size = 80;作为起始的地方完全是任意的。我们可以使用81或79或100,或者将它作为用户提供的参数添加到函数中。我们甚至可以添加int (*inc)(int)参数,并将size *= 2;更改为size = inc(size);,从而允许用户控制阵列的增长速度。当重新分配成本高昂并需要读取和处理大量的数据时,这些对于效率很有用。

我们可以写同样与scanf,但想到我们就会有多少次重写格式字符串。我们可以将它限制为一个常量增量,而不是上面实现的加倍(容易),而不必调整格式字符串;我们可以让步,只储存数量,做数学题与上面,并用snprintf它每次我们重新分配时间转换格式字符串使scanf可以将其转换回相同数量的;我们可以通过手动调整格式字符串(比如增加数字)来限制我们的增长和起始位置,但这会在一段时间后变得毛茸茸,并且可能需要递归(!)才能干净地工作。

此外,它很难与scanf与其他功能混合读取读数。为什么?假设您想从一行读取一个整数,然后从下一行读取一个字符串。你试试这个:

int i; 
char buf[BUSIZE]; 
scanf("%i", &i); 
fgets(buf, BUFSIZE, stdin); 

将读取的“2”,但随后fgets会读一个空行,因为scanf没读过一行!好吧,采取两种:

... 
scanf("%i\n", &i); 
... 

您认为这吃了换行,它 - 但它也吃了下一行开头的空白,因为scanf不能告诉换行符和其他形式之间的区别空白。 (另外,原来你写一个Python语法分析器,以及线前导空格很重要)为了使这项工作,你必须调用getchar或某事在换行符阅读和扔掉它:

... 
scanf("%i", &i); 
getchar(); 
... 

不是很傻吗?如果您使用的功能scanf,但不叫getchar,因为你不知道下一个读是否将是scanf什么理智的,会发生什么(或者判断下一个字符,甚至将是一个换行符) ?突然间,处理这种情况的最好方法似乎是挑选其中一种:我们是否专门使用scanf,并且永远不能访问fgets风格的全控制输入,或者我们专门使用fgets,并且难以执行复杂的解析?

其实,答案是我们不。我们使用fgets(或非scanf功能)完全,而当我们需要scanf样的功能,我们只是呼吁串sscanf我们不需要有scanf不必要地浪费我们的文件流!我们可以对我们想要的输入进行精确控制,并且仍然获得scanf格式的所有功能。即使我们不能,很多scanf格式选项在标准库中都有近乎直接的对应功能,如无限灵活的strtolstrtod功能(和朋友)。此外,i = strtoumax(str, NULL)为C99大小的整数类型是有很多清洁看上去比scanf("%" SCNuMAX, &i);,和安全很多(我们可以使用strtoumax线不变较小的类型,让隐式转换手柄的额外位,但与scanf我们必须做出一个临时uintmax_t读入)。

这个故事的寓意:避免scanf。如果您需要它提供的格式,并且不希望(或不能)(更高效地)执行此操作,请使用fgets/sscanf