Есть множество проблем с этим кодом. Мы исправим плохо названные переменные и функции и исследуем проблемы:
Во-первых, CharToInt()
следует переименовать на правильный StringToInt()
, поскольку он работает с строкой , а не одним символом.
Функция CharToInt()
[sic.] Небезопасна. Он не проверяет, случайно ли пользователь передал нулевой указатель.
Не проверяет ввод, или, что более правильно, пропускает неверный ввод. Если пользователь вводит не-цифру, результат будет содержать фиктивное значение. т.е. если вы введете N
, то код *(s+i) & 15
будет выдавать 14!?
Далее, невзрачный temp
в CharToInt()
[sic.] Должен называться digit
, поскольку это действительно так.
Кроме того, kludge return result / 10;
- это просто плохой хак для обхода ошибочной реализации.
Аналогично MAX
имеет неправильное название, так как может показаться, что он конфликтует со стандартным использованием. т.е. #define MAX(X,y) ((x)>(y))?(x):(y)
Подробный *(s+i)
не так удобен для чтения, как просто *s
. Нет необходимости использовать и загромождать код еще одним временным индексом i
.
получает ()
Это плохо, потому что может переполнить буфер входной строки. Например, если размер буфера равен 2 и вы вводите 16 символов, вы переполнитесь str
.
зсапЕ ()
Это в равной степени плохо, поскольку может переполнить буфер входной строки.
Вы упоминаете " при использовании функции scanf (), результат совершенно неверный, потому что первый символ, очевидно, имеет значение -52 ASCII. "
Это связано с неправильным использованием scanf (). Я не смог продублировать эту ошибку.
fgets ()
Это безопасно, потому что вы можете гарантировать, что никогда не переполните буфер входной строки, передавая размер буфера (который включает в себя пространство для NULL.)
GetLine ()
Несколько человек предложили стандарт C POSIX getline()
в качестве замены. К сожалению, это не практичное портативное решение, так как Microsoft не реализует версию C; только стандартная функция шаблона строки C ++ , так как этот вопрос 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
, нам нужно только проверить, установлены ли старшие 3 мсб (наиболее значимые биты).
Кроме того, необходим второй тест на переполнение. Если 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;
}