这段代码有很多问题。我们将修复命名错误的变量和函数并调查问题:
首先,CharToInt()
应该重命名为正确的StringToInt()
,因为它对字符串而不是单个字符进行操作。
函数CharToInt()
[sic.] 是不安全的。它不检查用户是否不小心传入了 NULL 指针。
它不验证输入,或者更准确地说,跳过无效输入。如果用户输入非数字,结果将包含虚假值。ie如果你输入N
代码*(s+i) & 15
会产生14!?
接下来,应该调用 [sic.]temp
中的非描述性,因为它确实是这样。CharToInt()
digit
此外,kludgereturn result / 10;
就是这样——解决有缺陷的实现的一个糟糕的hack 。
同样MAX
的名字很糟糕,因为它可能看起来与标准用法冲突。IE#define MAX(X,y) ((x)>(y))?(x):(y)
详细*(s+i)
不如简单易读*s
。没有必要使用另一个临时索引来混淆代码i
。
获取()
这很糟糕,因为它可能会溢出输入字符串缓冲区。例如,如果缓冲区大小为 2,而您输入 16 个字符,则会溢出str
。
扫描函数()
这同样糟糕,因为它会溢出输入字符串缓冲区。
您提到“使用 scanf() 函数时,结果完全错误,因为第一个字符显然具有 -52 ASCII 值。 ”
这是由于 scanf() 的错误使用造成的。我无法复制此错误。
fgets()
这是安全的,因为您可以通过传入缓冲区大小(包括 NULL 的空间)来保证永远不会溢出输入字符串缓冲区。
获取线()
一些人建议使用 C POSIX 标准 getline()
作为替代。不幸的是,这不是一个实用的可移植解决方案,因为微软没有实现 C 版本;只有标准 C++字符串模板函数作为这个 SO #27755191问题的答案。Microsoft 的 C++getline()
至少早在Visual Studio 6中就可以使用,但由于 OP 严格询问 C 而不是 C++,因此这不是一个选项。
杂项。
最后,这个实现是错误的,因为它没有检测到整数溢出。如果用户输入的数字太大,数字可能会变成负数!即9876543210
会变成-18815698
?!让我们也解决这个问题。
对于unsigned int
. 如果前一个部分数小于当前部分数,那么我们已经溢出,我们返回前一个部分数。
对于 asigned int
这是一个多一点的工作。在汇编中我们可以检查进位标志,但在 C 语言中没有标准的内置方法来检测带符号 int 数学的溢出。幸运的是,由于我们乘以一个常数 ,* 10
如果我们使用等价方程,我们可以很容易地检测到这一点:
n = x*10 = x*8 + x*2
如果 x*8 溢出,那么逻辑上 x*10 也会溢出。当 x*8 = 0x100000000 时会发生 32 位 int 溢出,因此我们需要做的就是检测 x >= 0x20000000 的时间。由于我们不想假设有多少位,int
我们只需要测试是否设置了前 3 个 msb(最高有效位)。
此外,还需要进行第二次溢出测试。如果在数字连接之后设置了 msb(符号位),那么我们也知道数字溢出。
代码
这是一个固定的安全版本以及可以用来检测不安全版本中的溢出的代码。我signed
还unsigned
通过#define SIGNED 1
#include <stdio.h>
#include <ctype.h> // isdigit()
// 1 fgets
// 2 gets
// 3 scanf
#define INPUT 1
#define SIGNED 1
// re-implementation of atoi()
// Test Case: 2147483647 -- valid 32-bit
// Test Case: 2147483648 -- overflow 32-bit
int StringToInt( const char * s )
{
int result = 0, prev, msb = (sizeof(int)*8)-1, overflow;
if( !s )
return result;
while( *s )
{
if( isdigit( *s ) ) // Alt.: if ((*s >= '0') && (*s <= '9'))
{
prev = result;
overflow = result >> (msb-2); // test if top 3 MSBs will overflow on x*8
result *= 10;
result += *s++ & 0xF;// OPTIMIZATION: *s - '0'
if( (result < prev) || overflow ) // check if would overflow
return prev;
}
else
break; // you decide SKIP or BREAK on invalid digits
}
return result;
}
// Test case: 4294967295 -- valid 32-bit
// Test case: 4294967296 -- overflow 32-bit
unsigned int StringToUnsignedInt( const char * s )
{
unsigned int result = 0, prev;
if( !s )
return result;
while( *s )
{
if( isdigit( *s ) ) // Alt.: if (*s >= '0' && *s <= '9')
{
prev = result;
result *= 10;
result += *s++ & 0xF; // OPTIMIZATION: += (*s - '0')
if( result < prev ) // check if would overflow
return prev;
}
else
break; // you decide SKIP or BREAK on invalid digits
}
return result;
}
int main()
{
int detect_buffer_overrun = 0;
#define BUFFER_SIZE 2 // set to small size to easily test overflow
char str[ BUFFER_SIZE+1 ]; // C idiom is to reserve space for the NULL terminator
printf(" Enter some numbers (no spaces): ");
#if INPUT == 1
fgets(str, sizeof(str), stdin);
#elif INPUT == 2
gets(str); // can overflows
#elif INPUT == 3
scanf("%s", str); // can also overflow
#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;
}