在简单的等式中用 scanf 替换 gets 会使程序崩溃
Replacing gets with scanf in simple equation crashes the program
我正在学习 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);
}
程序启动,但输入后就崩溃了。我哪里错了?
您在转换时遇到问题。
atoi 接受一个 const char* 作为输入。
您正在传递一个 char,因此它隐式地将 char 转换为一个点,糟糕的事情。
根据 user3121023 的建议,将 height_in_inches 更改为字符串。
char height_in_inches[20];
并使用 %s 阅读
scanf("%s", height_in_inches);
既然 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
和 [=13 的例子=] 和 printf()
。鉴于下面的评论,它们目前与 OP 无关。
对原始代码的最少修复
由于上面的代码比 OP 识别的更复杂,这里是对原始代码的一组更简单的修复。将字符串输入到数组(string)中是关键点;这也需要在 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);
}
一行输入有多长?
a LOT of stack space can be saved by:
- noticing that an 'int' is only a max of 12 characters so the max length on the input buffer is 13 characters (to allow for the NUL string termination byte)
- limit the
scanf()
to 12 characters. I.E. 'scanf( "%12s", myCharArray );
- in C, the name of an array degrades to the address of the array, so not leading '&' needed on '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()
函数来读取该行。它为整行分配了足够的 space ,除非内存不足。对于大多数学生练习作业,4 KiB 缓冲区和 fgets()
是一个合理的替代品。只要指数至少为 8,我愿意就使用的 2 的幂进行协商 — 值至少为 256。例如,将行缓冲区限制为 80 个字符不会阻止用户输入超过一行 80 个字符。这只是意味着额外的字符不太可能得到适当的处理。将缓冲区限制为 13 个字符不会给你带来任何有价值的东西,IMO,'saving stack space' 是一个过早的优化。
我正在学习 C for Dummies。它有一个使用 gets
和 atoi
.
现在,我想尝试使用 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);
}
程序启动,但输入后就崩溃了。我哪里错了?
您在转换时遇到问题。
atoi 接受一个 const char* 作为输入。
您正在传递一个 char,因此它隐式地将 char 转换为一个点,糟糕的事情。
根据 user3121023 的建议,将 height_in_inches 更改为字符串。
char height_in_inches[20];
并使用 %s 阅读
scanf("%s", height_in_inches);
既然 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
和 [=13 的例子=] 和 printf()
。鉴于下面的评论,它们目前与 OP 无关。
对原始代码的最少修复
由于上面的代码比 OP 识别的更复杂,这里是对原始代码的一组更简单的修复。将字符串输入到数组(string)中是关键点;这也需要在 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);
}
一行输入有多长?
a LOT of stack space can be saved by:
- noticing that an 'int' is only a max of 12 characters so the max length on the input buffer is 13 characters (to allow for the NUL string termination byte)
- limit the
scanf()
to 12 characters. I.E. 'scanf( "%12s", myCharArray );
- in C, the name of an array degrades to the address of the array, so not leading '&' needed on '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()
函数来读取该行。它为整行分配了足够的 space ,除非内存不足。对于大多数学生练习作业,4 KiB 缓冲区和 fgets()
是一个合理的替代品。只要指数至少为 8,我愿意就使用的 2 的幂进行协商 — 值至少为 256。例如,将行缓冲区限制为 80 个字符不会阻止用户输入超过一行 80 个字符。这只是意味着额外的字符不太可能得到适当的处理。将缓冲区限制为 13 个字符不会给你带来任何有价值的东西,IMO,'saving stack space' 是一个过早的优化。