在简单方程中用 scanf 替换 get 会使程序崩溃
Posted
技术标签:
【中文标题】在简单方程中用 scanf 替换 get 会使程序崩溃【英文标题】:Replacing gets with scanf in simple equation crashes the program 【发布时间】:2015-06-07 21:22:49 【问题描述】:我正在使用 C for Dummies。它有一个使用 gets
和 atoi
将英寸转换为厘米的示例代码(第 135 页,如果你有文本)。
现在,我想尝试使用scanf
而不是可怕的gets
,这是我能想到的最好的方法(基于作者提供的代码)。
#include <stdio.h>
#include <stdlib.h>
int main()
float height_in_cm;
char height_in_inches;
printf("Enter your height in inches: ");
scanf("%c"),&height_in_inches;
height_in_cm = atoi(height_in_inches)*2.54;
printf("You are %.2f centimetres tall.\n",height_in_cm);
return(0);
程序启动,但输入后就崩溃了。我哪里错了?
【问题讨论】:
你确定这条线:scanf("%c"),&height_in_inches; ?我认为应该是 scanf("%c",&height_in_inches); 您可以在此处查看scanf函数(cplusplus.com/reference/cstdio/scanf)了解更多正确使用示例 错误:scanf("%c"),&height_in_inches;
。更好:scanf("%c", &height_in_inches);
。假设您要输入一个二进制的 8 位整数值(例如,对于“10”,请在键盘上输入 。如果您想要一个 STRING,(如字符“10 "); 我建议:BEST:int height_in_inches; scanf ("%d", &height_in_inches);
在任何一种情况下,你都不需要 "atoi()"...
@paulsm4 我同意你想使用 %d。我确实尝试过使用它,但是当它不起作用时,我不确定要更改哪个部分,所以这是其中之一。
实际上,Jonathan Leffler 的(新!)答案是最好的。总结:1)您最初的崩溃是因为“scanf()”语法不正确。 2)如果你想输入一个字符串,那么使用1字节的“char”也是错误的:你应该声明一个n字节的“char[]”数组。 3) 但坦率地说,让“scanf”为您完成工作并声明“height_in_inches”为您最终需要的类型(例如“float”或“double”)是最有意义的。强烈建议:再看看 Jonathan Leffler 的回复。
【参考方案1】:
既然scanf()
可以帮你转换答案,为什么还要花时间转换答案?
#include <stdio.h>
int main(void)
float height_in_inches;
printf("Enter your height in inches: ");
if (scanf("%f", &height_in_inches) == 1)
float height_in_cm = height_in_inches * 2.54;
printf("You are %.2f inches or %.2f centimetres tall.\n",
height_in_inches, height_in_cm);
else
printf("I didn't understand what you said\n");
return(0);
如果你必须读取一个字符串,那么使用fgets()
:
#include <stdio.h>
#include <stdlib.h>
int main(void)
char line[4096];
printf("Enter your height in inches: ");
if (fgets(line, sizeof(line), stdin) != 0)
double height_in = strtod(line, 0);
double height_cm = height_in * 2.54;
printf("You are %.2f inches or %.2f centimetres tall.\n",
height_in, height_cm);
return(0);
请注意,两个程序都会在使用输入结果之前检查输入是否发生。你可以争辩说,对strtod()
的调用的错误检查是非常懒惰的。我同意。请注意,我在第一个片段中的float
和第二个片段中的double
之间切换;要么可以工作。当结果为分数时,我认为没有特别的理由将输入限制为整数值。回显输入和输出通常也是有益的;如果你得到的回应不是你认为你输入的,这是一个很好的提示,表明某些地方出了问题,并让你在代码的正确区域进行搜索。
注意:有一些小细节被掩盖在地毯下,尤其是在第一个例子中,float
和 double
与 scanf()
和 printf()
。鉴于下面的评论,它们目前与 OP 无关。
对原始代码的最小修复
由于上面的代码比 OP 识别的更复杂,这里是对原始代码的一组更简单的修复。输入到数组(字符串)中的字符串是关键点;这也需要更改 scanf()
调用。
通过使用大缓冲区,我们可以假设用户无法通过在终端键入来溢出输入。但是,对于机器驱动的输入来说就不行了。
#include <stdio.h>
#include <stdlib.h>
int main(void)
float height_in_cm;
char height_in_inches[4096]; // Array, big enough to avoid most overflows
printf("Enter your height in inches: ");
// Missing error check on scanf() — too advanced as yet
scanf("%s", height_in_inches); // Format specifier, parentheses, ampersand
height_in_cm = atoi(height_in_inches) * 2.54;
printf("You are %s inches or %.2f centimetres tall.\n",
height_in_inches, height_in_cm);
return(0);
一行输入有多长?
user3629249commented:
可以通过以下方式节省大量堆栈空间:
注意到“int”最多只能包含 12 个字符,因此输入缓冲区的最大长度为 13 个字符(以允许 NUL 字符串终止字节) 将
scanf()
限制为12 个字符。 IE。 'scanf( "%12s", myCharArray );
在 C 中,数组的名称降级为数组的地址,因此“myCharArray”上不需要前导“&”。
第 3 点是正确的;如果您使用char myCharArray[13];
,则在调用scanf()
等时不要使用&myCharArray
;你只使用myCharArray
。如果您滥用&
,一个好的编译器会指出您的方式错误。
不过,我对第 1 点和第 2 点有疑问。 很多的麻烦可以通过注意如果用户在线输入 9876432109876543210 来避免,那么使用 scanf()
和 %12s
对消除无效输入没有太大帮助。它将在行上留下 8 个未读数字,而读取的内容仍会溢出一个 32 位整数。如果您在较长的字符串上使用strtoX()
系列函数而不是atoi()
,那么它们会检测到诸如溢出之类的问题,而scanf()
和%d
和atoi()
都不会。 (这是主要答案中掩盖的众多观点之一。)
此外,在具有兆字节(通常是千兆字节)主内存的系统上,堆栈上的 4 KiB 不是主要问题。也就是说,我使用 4 KiB 部分是因为它的冲击值;但 POSIX 要求 [LINE_MAX] 的最小值为 2048。
如果您正在阅读基于行的输入,这通常是在命令行应用程序中进行输入的好方法,那么您需要确保阅读整行,因为处理部分行会很混乱并且会导致错误报告难的。 fgets()
加上sscanf()
处理的一个主要优点是您可以在错误报告中使用完整的行,而不是scanf()
在处理部分行后留下的内容。如果第一次尝试失败,您也可以尝试以不同的方式扫描字符串; scanf()
系列中的直接文件 I/O 函数无法做到这一点。
如果您天真地没有认识到人们在您希望他们输入短行时会输入长行,那么您可以将剩菜作为新行 - 无需进一步的用户交互 - 当它实际上是前一行的渣滓时输入。例如,如果您扫描 20 位数字中的 12 位,那么下一个输入将获得剩余的 8 位数字,而无需等待用户输入任何新内容,即使您提示他们输入更多信息。 (另外,请注意using fflush(stdin)
;它是否有任何用处,充其量是特定于系统的。)
我使用 fgets()
和 4 KiB 缓冲区。如果您想防止程序在单行上发送数兆字节的 JSON 编码数据,则需要使用 POSIX 的 getline()
函数来读取该行。它为整行分配足够的空间,除非内存不足。对于大多数学生的练习作业,4 KiB 缓冲区和fgets()
是一个合理的替代品。只要指数至少为 8,我愿意就使用的 2 的幂进行协商——该值至少为 256。例如,将行缓冲区限制为 80 个字符并不会阻止用户输入超过一行 80 个字符。这只是意味着额外的字符不太可能得到适当的处理。将缓冲区限制为 13 个字符不会给您带来任何有价值的东西,IMO,而且“节省堆栈空间”是一种过早的优化。
【讨论】:
你的例子,尤其是第二个,有点超前了。我真的是一个初学者。虽然我很高兴从目前所见的情况中了解if
和else
。
很抱歉超出了您所学的范围。我没有这本书,但在输入大量内容之前,我会期待if
、while
和for
的基本报道——显然,我错了。一两周后回来;那样会更有意义。要带走的一个关键点是输入操作(特别是)可能会失败,并且在使用结果之前应该检查输入操作的成功/失败。回应您阅读的内容是个好主意。如果您认为自己输入了 10,但计算机认为您输入了 31415332132,那么它会很好地提示您有问题。
我明白了,这确实有道理。这本书非常“这个做这个,那个做那个”,在我的发现点上是可以的,但是当我弄乱这些例子时它会引起问题。尽管如此,K&R 还是太过分了,所以我现在很高兴。
大量堆栈空间可以通过以下方式节省 1) 注意到“int”最多只有 12 个字符,因此输入缓冲区的最大长度为 13 个字符(以允许 NUL 字符串终止byte) 2) 将 scanf() 限制为 12 个字符。 IE。 'scanf("%12s", myCharArray); 3) 在 C 中,数组的名称降级为数组的地址,因此在 'myCharArray' 上不需要前导 '&'
@user3629249:查看解决您观察的答案的广泛补充。【参考方案2】:
你的演员阵容有问题。
atoi 接受 const char* 作为输入。
您正在传递一个 char,因此它隐式地将 char 转换为一个点,这是一件坏事。
根据 user3121023 的建议,将 height_in_inches 更改为字符串。
char height_in_inches[20];
并使用 %s 读取
scanf("%s", height_in_inches);
【讨论】:
@Ducoodi 想避免使用gets()
,但你建议使用scanf()
和%s
?至少使用%19s
以防止潜在的缓冲区溢出。
提供可编译的代码是明智的。 char [20] height_in_inches;
不适用于大多数 C 编译器(当然应该是 char height_in_inches[20];
)。
通常的约定是指定完整的缓冲区大小,但标准 I/O 库早于该约定的广泛使用,它选择了可以存储的字符串长度。在char buffer[20];
数组中,您可以存储最多19 个字符加上空终止字节的字符串。 strlen()
也不计算空终止字节。这是一个轻微的不一致,旨在使初学者的生活更加困难。嗯,不是真的。它只是 30 多年发展的遗产(除非您认为该设计是在 70 年代后期制作并在 80 年代后期冻结的)。
对不起我的错 char[] 是在 C# 而不是 C.
@Jonathan Leffler Minor 不同意字符串 20
与 19
理性。 "%19s"
中的数字 19
表示要扫描的最大(非空白)字符 - 它是扫描宽度。这与"%3d"
、"%7f"
和所有限制扫描宽度的说明符一致。 width 参数并不代表存储对象所需的空间。只是对于字符串,所需的大小是宽度 + 1。OTOH,如果原始 scanf()
提供更简单的动态字符串大小限制,那就太好了。以上是关于在简单方程中用 scanf 替换 get 会使程序崩溃的主要内容,如果未能解决你的问题,请参考以下文章