尽管有0长度的缓冲区和编译器警告,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  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"); } 

究竟发生了什么,我该如何解决?

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

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

你需要这样的东西:

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

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

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

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

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

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

只需改变这个:

 scanf("%s", &name); 

至:

 scanf("%39s", name); 

还有这个:

 char name[0]; 

至:

 char name[40]; 

另外你必须用’\ 0’结束它:

 name[39] = '\0'; 

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

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

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

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

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


顺便说一句,根据我的理解, char foo[ 0 ]是故意有效的 – 它可能起源于hack并且具有令人困惑的类型,但是并不常见地依赖于旧技巧来创建可变大小的结构,如本页所述来自GCC在线文档

  1. char name [0]; —> char name [100];
    / *你需要分配一些内存来存储名称* /

2.scanf(“%s”,&name); —-> scanf(“%s”,name); / * scanf将char *作为参数,因此您只需要传递字符串名称。 * /

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

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

/

 * * Program: gretting2.c * Utility: Display a greeting with your name. * Author: Adrián Garro. */ #include  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"); } 

因为正确的方法是

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

是什么

 char name[0]; 

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

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

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 

} // end function:main

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

 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()