このコードには多くの問題があります。不適切な名前の変数と関数を修正し、問題を調査します。
まず、単一の文字ではなく文字列を操作するCharToInt()
ため、適切な名前に変更する必要があります。StringToInt()
関数CharToInt()
[sic.] は安全ではありません。ユーザーが誤って NULL ポインターを渡したかどうかはチェックしません。
入力を検証しません。より正確には、無効な入力をスキップします。ユーザーが数字以外を入力すると、結果には偽の値が含まれます。つまりN
、コードを入力*(s+i) & 15
すると、14 !? が生成されます。
次に、[sic.] の nondescript を呼び出す必要がtemp
あります。CharToInt()
digit
また、クラッジはまさにそれです-バグのある実装を回避するためreturn result / 10;
の悪いハックです。
同様MAX
に、標準的な使用法と競合するように見える可能性があるため、不適切な名前が付けられています。すなわち#define MAX(X,y) ((x)>(y))?(x):(y)
verbose*(s+i)
は、単純なほど読みやすくありません*s
。さらに別の一時インデックスを使用してコードを乱雑にする必要はありませんi
。
取得()
入力文字列バッファがオーバーフローする可能性があるため、これは良くありません。例えば、バッファサイズが 2 の場合、16 文字で入力するとオーバーフローしstr
ます。
scanf()
これは、入力文字列バッファをオーバーフローさせる可能性があるため、同様に悪いことです。
「scanf() 関数を使用すると、最初の文字が明らかに-52 ASCII値を持っているため、結果は完全に間違っています。」
これは、scanf() の使い方が間違っているためです。このバグを再現できませんでした。
fgets()
バッファ サイズ (NULL の余地を含む) を渡すことで、入力文字列バッファがオーバーフローしないことを保証できるため、これは安全です。
getline()
C POSIX 標準 getline()
を代替として提案する人もいます。残念ながら、Microsoft は C バージョンを実装していないため、これは実用的な移植可能なソリューションではありません。この SO #27755191 の質問の回答として、標準の C++文字列テンプレート関数のみ。Microsoft の C++は、少なくともVisual Studio 6までさかのぼって利用できましたが、OP は C++ ではなく C について厳密に質問しているため、これはオプションではありません。getline()
その他
最後に、この実装は整数オーバーフローを検出しないという点でバグがあります。ユーザーが入力した数値が大きすぎると、数値が負になることがあります。すなわち9876543210
なる-18815698
でしょう?それも直しましょう。
これを修正するのは簡単unsigned int
です。前の部分数が現在の部分数より小さい場合は、オーバーフローしており、前の部分数を返します。
の場合、signed int
これはもう少し作業です。アセンブリではキャリー フラグを調べることができますが、C では、signed int 演算でオーバーフローを検出する標準の組み込み方法がありません。幸いなことに、定数 を掛けている* 10
ため、同等の方程式を使用すると、これを簡単に検出できます。
n = x*10 = x*8 + x*2
x*8 がオーバーフローする場合、論理的には x*10 もオーバーフローします。32 ビットの int オーバーフローは、x*8 = 0x100000000 のときに発生するため、x >= 0x20000000 を検出するだけで済みます。のビット数を想定したくないのでint
、上位 3 つの msb (最上位ビット) が設定されているかどうかだけをテストする必要があります。
さらに、2 回目のオーバーフロー テストが必要です。桁連結の後に msb が設定されている場合 (符号ビット)、数値がオーバーフローしたこともわかります。
コード
これは、安全でないバージョンのオーバーフローを検出するために使用できるコードとともに、修正された安全なバージョンです。signed
また、 aと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;
}