尽管有0长度的缓冲区和编译器警告,scanf()仍可正常工作。到底是怎么回事?

时间:2015-01-05 23:31:31

标签: c string pointers scanf

我的编译器(clang)显示以下消息:

11:17:warning: format specifies type 'char *' but the argument has
      type 'char (*)[0]' [-Wformat]
    scanf("%s", &name);
           ~~   ^~~~~
1 warning generated.

来自以下代码(问候程序):

/*
 * Program: gretting2.c
 * Utility: Display a greeting with your name.
 * Author:  Adrián Garro.
 */

#include <stdio.h>

int main () {

    char name[0];

    printf("-------------------\n");
    printf("Write your name: \n");
    printf("-------------------\n");

    scanf("%s", &name);

    printf("------------------------------------\n");
    printf("Hello %s, nice to meet you\n",name);
    printf("------------------------------------\n");
}

实际发生了什么,我该如何解决?

8 个答案:

答案 0 :(得分:4)

为了理解这一点,您必须了解scanf正在做什么。在这种情况下,scanf是从stdin读取一个字符串,并将其放入您提供的缓冲区中。 它不会为您分配该空间,也不会检测溢出。您需要为字符串分配足够的空间。就像现在一样,你正在为你的字符串分配空间,所以一切都是溢出的。这是一个主要的错误。

代替char[0],你做了char[40],正如另一位用户建议的那样。如果你的程序用户写了超过40个字符怎么办?这导致未定义的行为。从本质上讲,它会写入你不希望它写入的内存。它可能会导致段错误,可能导致重要内存被覆盖,或者可能会发生。这是scanf.的弱点。查看fgets.您告诉它缓冲区的大小,输入将被截断以适应。

当然,这与你的警告无关。您收到警告是因为引用数组的名称与引用指向其第一个元素的指针(即name <==> &(name[0]))相同。把指针指向这就像是指针指针,即&name <==> &&(name[0])。由于scanf正在寻找char*类型的参数,并且它正在获取指向该参数的指针,因此类型检查器会抱怨。

答案 1 :(得分:3)

只需改变一下:

scanf("%s", &name);

为:

scanf("%39s", name);

和此:

char name[0];

为:

char name[40];

此外,您还必须以&#39; \ 0&#39;用:

name[39] = '\0'; 

答案 2 :(得分:3)

您的代码显示“未定义的行为”。这意味着一切都可能发生。任何东西。

您正在将零长度数组传递给scanf()。此外,您没有在格式字符串中传递数组长度。这会导致缓冲区溢出漏洞(总是在零长度目标阵列的情况下)。

你需要这样的东西:

char name[51];
scanf("%50s", name);

注意%50s现在指定目标数组的大小(少一个,为空终止符留出空间!),这可以避免缓冲区溢出。您仍然需要检查scanf()的返回值,以及输入name实际上是否太长(您不希望在不告诉用户的情况下截断用户的输入)。

如果您使用的是Linux,请查看名为valgrind的工具。它是一个运行时内存错误检测器(除其他外),有时可以为你捕获这样的错误(而不是那么明显的错误,这是主要的观点)。它对许多C程序员来说都是不可或缺的。

答案 3 :(得分:1)

根据您希望的强大程度,您需要重新考虑这种方法。我想首先要知道你在声明char name[ 0 ]时是否理解了你正在使用的类型。这是一个零大小的&#39;字节大小的字符数组。这是一个令人困惑的事情,如果编译器的行为不同,我就不会感到惊讶......

编译器抱怨的实际警告是类型不匹配。如果您获取数组中第一个字符的地址,您可以摆脱它(即在&( name[ 0 ] )调用中使用scanfname的地址是它在堆栈上的位置 - 恰好是数组实现使用相同的位置来存储数组数据,并且编译器在单独使用时对name的处理方式不同以便数组的地址与其第一个元素的地址相同......

使用char name[ 0 ]会导致内存损坏,因为无法读取字符串,实现细节可能会运气不好并允许其工作。解决此问题的一种简单方法是将0替换为有意义的数字,并将其取为输入字符串的最大长度。说32,这样你就可以char name[ 32 ] ...但是,这并不能处理更长的字符串。

由于我们生活在一个拥有大量内存和大堆栈的世界中,你可以做char name[ 4096 ]并使用4KB内存作为缓冲区,这对于实际使用来说绝对没问题。

现在......如果你想成为一个小肛门并处理病态病例,就像用户靠着一些按键睡觉几个小时,然后按下输入并添加一些巨大的8000字符长的字符串,有几种方法来处理动态内存分配&#39;也可能超出了这个答案的范围。


顺便说一句,根据我的理解char foo[ 0 ]故意有效 - 它可能起源于黑客并且具有令人困惑的类型,但是并不常见地依赖于旧技巧来创建如上所述的可变大小的结构in this page from the GCC online docs

答案 4 :(得分:1)

  1. char name [0]; ---&GT; char name [100];
    / *您需要分配一些内存来存储名称* /
  2. 2.scanf(&#34;%s&#34;,&amp; name); ----&gt; scanf(&#34;%s&#34;,名称); / * scanf将char *作为参数,因此您只需要传递字符串名称。 * /

    我不认为scanf(&#34;%(长度 - 1)s&#34;,名称);需要。 因为%s用于读取字符串。这将在到达的第一个空白字符或指定的字段宽度(例如&#34;%39s&#34;)上停止,以先到者为准。

    除了这些不经常使用。当然,您可以随意使用它们!

    /

    *
     * Program: gretting2.c
     * Utility: Display a greeting with your name.
     * Author:  Adrián Garro.
     */
    
    #include <stdio.h>
    
    int main () {
    
        char name[100];
    
        printf("-------------------\n");
        printf("Write your name: \n");
        printf("-------------------\n");
    
        scanf("%s", name);
    
        printf("------------------------------------\n");
        printf("Hello %s, nice to meet you\n",name);
        printf("------------------------------------\n");
    }
    

答案 5 :(得分:0)

因为正确的方法是

scanf("%s", name);
        /* ^ no ampersand

什么是

char name[0];

您应指定非零长度并将其用于scanf长度说明符

scanf("%(length - 1)s", name);
        /*  ^ sunstitite with the value */

答案 6 :(得分:0)

OP发布的代码有几个问题

以下修复了大部分内容

我包括评论以指出问题所在

int main() {

//char name[0];  // this did not allow any room for the name
char  name[100] = {'\0'}; // declare a 100 byte buffer and init to all '\0'

printf("-------------------\n");
printf("Write your name:, max 99 char \n"); // 99 allows room for nul termination byte
printf("-------------------\n");

//scanf("%s", &name);  // this has no limit on length of input string so can overrun buffer
if( 1 == scanf("%99s", name) )   // 1) always check returned value from I/O functions
                                 // 2) no '&' before 'name' because
                                 //    arrays degrade to pointer to array when variable
                                 //    name is used
                                 // 3) placed max size limit on format conversion string
                                 //    so input buffer 'name' cannot be overflowed
{ // then scanf failed
    perror( "scanf failed for name" );
    return(-1);  // indicate error
}

// implied else, scanf successful

printf("------------------------------------\n");
printf("Hello %s, nice to meet you\n",name);
printf("------------------------------------\n");

return(0); // indicate success

} //结束函数:main

答案 7 :(得分:-1)

您正在阅读“字符串”,因此正确的方法是:

scanf("%s", name);

为什么编译器会抱怨?在scanf中提供参数时,可以提供变量的内存位置。例如:

int x;
scanf("%d", &x);

&xint *,即指向整数的指针,因此x将获得正确的值。

当你读一个字符串时,你实际上是在一起阅读许多char个变量。要存储它们,您需要一个char *数组;好吧,name本身就是char *,所以不需要写&name。后者是char **,即char的二维数组。

顺便说一句,您还需要为要读取的字符分配空间。因此,您必须编写char name[20](或任何其他数字)。您还需要在return 0;中提供int main()