这段代码存在
众多问题。我们将修复变量和函数的不良命名,并调查问题:
首先,CharToInt()
应该被重命名为适当的 StringToInt()
,因为它操作的是一个字符串而不是单个字符。
CharToInt()
函数不安全。它没有检查用户是否意外传递了一个空指针。
它不验证输入,或者更正确地说,跳过无效的输入。如果用户输入了非数字,则结果将包含错误值。例如,如果您输入 N
,则代码 *(s+i) & 15
将产生 14!?
接下来,在 CharToInt()
中不明确的 temp
应该被称为 digit
,因为那才是它真正的含义。
此外,CharToInt()
中的巧妙解决方案 return result / 10;
只是一个糟糕的补救措施,以解决有缺陷的实现问题。
同样,MAX
的命名不恰当,因为它可能与标准用法冲突。即 #define MAX(X,y) ((x)>(y))?(x):(y)
冗长的 *(s+i)
不如简单的 *s
读起来清晰。没有必要使用并且用另一个临时索引 i
来混淆代码。
gets()
这个函数很糟糕,因为它可能会溢出输入字符串缓冲区。例如,如果缓冲区大小为2,并且您输入了16个字符,则会溢出str
。
scanf()
这同样很糟糕,因为它可能会溢出输入字符串缓冲区。
您提到“使用scanf()函数时,结果完全错误,因为第一个字符显然具有-52的ASCII值。”
这是由于scanf()的不正确使用。我无法复制此错误。
fgets()
这个函数是安全的,因为您可以通过传递缓冲区大小(包括NULL的空间)来保证永远不会溢出输入字符串缓冲区。
getline()
一些人建议使用C的
POSIX标准getline()
来替代。不幸的是,这并不是一个实用的可移植解决方案,因为Microsoft没有实现C版本,只有标准C++
string模板函数,正如SO
#27755191问题所回答的那样。尽管Microsoft的C++
getline()
至少早在
Visual Studio 6就已经存在,但由于OP严格要求使用C而不是C ++,因此这不是一个选项。
其他
最后,这个实现有一个漏洞,它无法检测整数溢出。如果用户输入的数字太大,数字可能会变成负数!例如
9876543210
将变成
-18815698
?让我们也修复这个问题。
对于无符号整数unsigned int
,这个问题很容易解决。如果之前的部分数字小于当前部分数字,那么我们就发生了溢出,然后返回之前的部分数字。
对于有符号整数signed int
,需要做更多的工作。在汇编语言中,我们可以检查进位标志,但在C语言中,没有标准内置的方法来检测有符号整数计算的溢出。幸运的是,由于我们乘以一个常数* 10
,如果使用等效方程,我们就可以轻松地检测到这一点:
n = x*10 = x*8 + x*2
如果x*8溢出,那么逻辑上x*10也会溢出。对于32位int类型,当x*8=0x100000000时会发生溢出,因此我们只需要检测x是否大于等于0x20000000。由于我们不想假设int类型有多少位,所以我们只需要测试前三个最高有效位是否设置。
此外,需要进行第二次溢出测试。如果数字连接后msb(符号位)被设置,则我们也知道该数字已经溢出。
代码
这里是一个修复后的安全版本,以及您可以使用的代码,以检测不安全版本中的溢出。我还通过#define SIGNED 1包含了带符号和无符号版本。
#include <stdio.h>
#include <ctype.h>
#define INPUT 1
#define SIGNED 1
int StringToInt( const char * s )
{
int result = 0, prev, msb = (sizeof(int)*8)-1, overflow;
if( !s )
return result;
while( *s )
{
if( isdigit( *s ) )
{
prev = result;
overflow = result >> (msb-2);
result *= 10;
result += *s++ & 0xF;
if( (result < prev) || overflow )
return prev;
}
else
break;
}
return result;
}
unsigned int StringToUnsignedInt( const char * s )
{
unsigned int result = 0, prev;
if( !s )
return result;
while( *s )
{
if( isdigit( *s ) )
{
prev = result;
result *= 10;
result += *s++ & 0xF;
if( result < prev )
return prev;
}
else
break;
}
return result;
}
int main()
{
int detect_buffer_overrun = 0;
#define BUFFER_SIZE 2
char str[ BUFFER_SIZE+1 ];
printf(" Enter some numbers (no spaces): ");
#if INPUT == 1
fgets(str, sizeof(str), stdin);
#elif INPUT == 2
gets(str);
#elif INPUT == 3
scanf("%s", str);
#endif
#if SIGNED
printf(" Entered number is: %d\n", StringToInt(str));
#else
printf(" Entered number is: %u\n", StringToUnsignedInt(str) );
#endif
if( detect_buffer_overrun )
printf( "Input buffer overflow!\n" );
return 0;
}
unsigned char
解决了你的问题:一个普通(有符号)char
的值范围是-128到127,而一个unsigned char
的范围是0到255。位运算可能会对负值产生奇怪的影响。 - sigint*(s+i)
通常写成s[i]
(它们具有完全相同的语义)。 - cafatoi
和CharToInt
都存在一个问题,即如果要转换的数字大于INT_MAX
,则会导致未定义的行为。为了解决这个问题,您可以使用strtol
系列中的函数,或修改CharToInt
,使其在溢出而不是溢出时退出。(实际上,CharToInt
还需要进一步修改;按照当前的编写方式,它只能读取到INT_MAX / 10
;如果输入非数字,则会出现奇怪的情况) - M.Mprintf("Input: [[%s]]\n", str);
。转换函数通常会跳过前导空格并停止于第一个不能成为数字的字符。如果只有尾随空格(特别是换行符),通常不会生成错误。如果转换后的字符串后面还有其他非数字字符,则可能会生成错误,也可能不会。 - Jonathan Leffler