私は、文字列(数字が入力されていると仮定)を整数に変換するかなり簡単なプログラムを実行しています。
完了した後、主にscanf()
、gets()
、fgets()
の機能に関する知識が限られているため、答えられない非常に奇妙な「バグ」に気付きました。作業。 (しかし、私は多くの文献を読みました。)
したがって、あまり多くのテキストを書かずに、プログラムのコードを次に示します。
#include <stdio.h>
#define MAX 100
int CharToInt(const char *);
int main()
{
char str[MAX];
printf(" Enter some numbers (no spaces): ");
gets(str);
// fgets(str, sizeof(str), stdin);
// scanf("%s", str);
printf(" Entered number is: %d\n", CharToInt(str));
return 0;
}
int CharToInt(const char *s)
{
int i, result, temp;
result = 0;
i = 0;
while(*(s+i) != '\0')
{
temp = *(s+i) & 15;
result = (temp + result) * 10;
i++;
}
return result / 10;
}
だからここに私が抱えている問題があります。まず、gets()
関数を使用すると、プログラムは完全に機能します。
第二に、fgets()
を使用すると、明らかにfgets()
関数が改行(ASCII値10)文字を読み取って結果を台無しにするため、結果はわずかに間違っています。
第三に、scanf()
関数を使用する場合、最初の文字は明らかに-52 ASCII値。これについては説明がありません。
gets()
の使用は推奨されていないので、改行文字を読み取らない(または無視する)ためにfgets()
をここで使用できるかどうかを知りたいと思います。また、このプログラムのscanf()
関数の処理は何ですか?
Nevergets
を使用します。バッファーオーバーフローの脆弱性に対する保護は提供されません(つまり、渡すバッファーの大きさを伝えることができないため、ユーザーがバッファーより大きな行を入力してメモリを破壊することを防ぐことはできません)。
scanf
の使用は避けてください。慎重に使用しないと、gets
と同じバッファオーバーフローの問題が発生する可能性があります。それを無視しても 他の問題があり、正しく使用するのが難しくなります 。
一般に、代わりにfgets
を使用する必要がありますが、それは時々不便です(改行を削除する必要があります。事前にバッファサイズを決定する必要があります。あなたが読んだ部分を保持し、 余分な部分を捨てる 、全部を捨てる、動的にバッファを増やして再試行するなど)。この動的な割り当てを行う非標準の関数がいくつかあります(例:POSIXシステムではgetline
、 チャックファルコナーのパブリックドメインggets
関数)。 ggets
には、末尾の改行を削除するという意味でgets
のようなセマンティクスがあることに注意してください。
はい、gets
を避けたいです。 fgets
は、バッファーがそれを保持するのに十分な大きさである場合、常に改行を読み取ります(これにより、バッファーが小さすぎて読み取りを待機している行がまだあることがわかります)。改行を読み取らないfgets
のようなものが必要な場合(小さすぎるバッファの表示を失う)、"%N[^\n]"
のようなスキャンセット変換でfscanf
を使用できます。 「N」はバッファサイズ-1に置き換えられます。
fgets
で読み込んだ後、バッファから末尾の改行を削除する簡単な(奇妙な場合)方法は次のとおりです。strtok(buffer, "\n");
これはstrtok
の意図ではありません使用しましたが、意図した方法よりも頻繁にこの方法で使用しました(通常は避けます)。
このコードには多数の問題があります。間違った名前の変数と関数を修正し、問題を調査します。
まず、CharToInt()
は単一の文字ではなくstringで動作するため、適切なStringToInt()
に名前を変更する必要があります。
関数CharToInt()
[sic。]は安全ではありません。ユーザーが誤ってNULLポインターを渡したかどうかはチェックしません。
入力を検証しません。より正確には、無効な入力をスキップします。ユーザーが数字以外を入力すると、結果には偽の値が含まれます。つまり、N
を入力すると、コード*(s+i) & 15
は14を生成します!?
次に、CharToInt()
[sic。]の非記述子temp
は、digit
と呼ばれるべきです。
また、汚い_return result / 10;
_はまさにそれです-バグのある実装を回避するのに悪いhackです。
同様に、MAX
は標準の使用法と矛盾するように見えるため、不適切な名前です。すなわち#define MAX(X,y) ((x)>(y))?(x):(y)
詳細な*(s+i)
は、単に_*s
_ほど読みやすくありません。コードを使用して、さらに別の一時インデックスi
を使用する必要はありません。
入力文字列バッファをオーバーフローさせる可能性があるため、これは悪いことです。たとえば、バッファサイズが2で、16文字を入力すると、str
がオーバーフローします。
これは、入力文字列バッファをオーバーフローさせる可能性があるため、同様に悪いです。
「scanf()関数を使用する場合、最初の文字には明らかに-52 ASCII value。)があるため、結果は完全に間違っています」と述べています。
これは、scanf()の誤った使用方法によるものです。このバグを複製することはできませんでした。
これは、バッファサイズ(NULLの余地を含む)を渡すことにより、入力文字列バッファがオーバーフローしないことを保証できるため、安全です。
少数の人々は、C POSIX標準getline()
を代替として提案しています。残念ながら、MicrosoftはCバージョンを実装していないため、これは実用的なポータブルソリューションではありません。標準のC++のみ string template function as this SO #27755191 質問の答え。MicrosoftのC++ getline()
は Visual Studio 6 であるが、OPはC++ではなくCについて厳密に要求しているため、これはオプションではありません。
最後に、この実装は整数オーバーフローを検出しないという点でバグがあります。ユーザーが大きすぎる数値を入力すると、数値が負になる可能性があります!つまり、_9876543210
_は_-18815698
_ ?!になります!それも修正しましょう。
これは_unsigned int
_の修正は簡単です。前の部分番号が現在の部分番号よりも小さい場合、オーバーフローし、前の部分番号を返します。
_signed int
_の場合、これはもう少し作業です。 Assemblyでは、キャリーフラグを検査できますが、Cでは、signed int mathでオーバーフローを検出する標準の組み込み方法はありません。幸いなことに、定数_* 10
_を乗算しているので、同等の方程式を使用すると、これを簡単に検出できます。
_n = x*10 = x*8 + x*2
_
X * 8がオーバーフローすると、論理的にx * 10もオーバーフローします。 32ビット整数の場合、x * 8 = 0x100000000のときにオーバーフローが発生するため、x> = 0x20000000のときに検出するだけです。 int
にいくつのビットがあるかを想定したくないので、上位3 msb(Most Significant Bits)が設定されているかどうかをテストするだけです。
さらに、2番目のオーバーフローテストが必要です。桁の連結の後にmsbが設定されている場合(符号ビット)、数字がオーバーフローしたこともわかります。
修正された安全なバージョンと、安全でないバージョンのオーバーフローを検出するために使用できるコードを次に示します。 _#define SIGNED 1
_経由でsigned
とunsigned
の両方のバージョンも含めました
_#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;
}
_
gets
を使用しないでください。 fgets
を使用する場合は、単に改行を上書きできます。
char *result = fgets(str, sizeof(str), stdin);
char len = strlen(str);
if(result != NULL && str[len - 1] == '\n')
{
str[len - 1] = '\0';
}
else
{
// handle error
}
これは、埋め込まれたNULLがないことを前提としています。別のオプションはPOSIX getline
:
char *line = NULL;
size_t len = 0;
ssize_t count = getline(&line, &len, stdin);
if(count >= 1 && line[count - 1] == '\n')
{
line[count - 1] = '\0';
}
else
{
// Handle error
}
getline
の利点は、割り当てと再割り当てを行い、埋め込みNULLを処理し、カウントを返すため、strlen
で時間を無駄にする必要がないことです。 getline
では配列を使用できないことに注意してください。ポインターはNULL
またはフリーである必要があります。
scanf
でどのような問題が発生しているかわかりません。
gets()を使用しないでください。予測できないオーバーフローが発生する可能性があります。文字列配列のサイズが1000で、1001文字を入力すると、プログラムをバッファオーバーフローできます。
CharToInt()のこの修正バージョンでfgets()を使用してみてください。
int CharToInt(const char *s)
{
int i, result, temp;
result = 0;
i = 0;
while(*(s+i) != '\0')
{
if (isdigit(*(s+i)))
{
temp = *(s+i) & 15;
result = (temp + result) * 10;
}
i++;
}
return result / 10;
}
基本的に、入力数字を検証し、それ以外は無視します。これは非常に粗雑なので、味と塩を変更します。