C - scanf() vs gets() vs fgets()
저는 일련의 문자(숫자가 입력된 것으로 가정)를 정수로 변환하는 상당히 쉬운 프로그램을 수행해 왔습니다.
제가 끝난 후, 저는 제가 대답할 수 없는 매우 특이한 "버그"들을 알아차렸습니다. 주로 어떻게 해야 하는지에 대한 제한된 지식 때문입니다.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()
function은 새 줄(ASCII 값 10) 문자를 마지막으로 읽으며 결과를 망칩니다.
째셋을 할 때, 시용사를 합니다.scanf()
함수입니다. 첫 번째 문자가 -52 ASCII 값을 가지고 있기 때문에 결과가 완전히 잘못되었습니다.이에 대해 저는 설명할 수 없습니다.
저는 그것을 .gets()
사용을 권장하지 않습니다. 그래서 저는 제가 사용할 수 있는지 알고 싶습니다.fgets()
여기서 새 줄 문자를 읽지 않도록(또는 무시합니다).또한, 그 일은 어떻게 된 겁니까?scanf()
이 프로그램의 기능?
사용 안 함
gets
버퍼 오버플로 취약성에 대한 보호 기능을 제공하지 않습니다. 즉, 버퍼 오버플로 취약성에 전달되는 버퍼 크기를 사용자가 알 수 없으므로 사용자가 버퍼 및 클로버 메모리보다 큰 줄을 입력하는 것을 방지할 수 없습니다.사용을 피합니다.
scanf
주의해서 사용하지 않으면 동일한 버퍼 오버플로 문제가 발생할 수 있습니다.gets
그것을 무시하더라도, 그것은 정확한 사용을 어렵게 만드는 다른 문제들을 가지고 있습니다.으로 일적으사합니다야해를 사용해야 .
fgets
대신에, 때때로 불편하기는 하지만 (새 줄을 벗기고, 버퍼 크기를 미리 결정해야 하며, 너무 긴 줄을 어떻게 처리해야 하는지를 파악해야 합니다.) 읽었던 부분을 유지하고 초과 부분을 폐기하고 전체를 폐기하고 버퍼를 동적으로 확장하고 다시 시도하는 등의 작업을 수행해야 합니다.이러한 동적 할당을 수행하는 일부 비표준 함수(예:getline
POSIX 시스템에서 Chuck Falconer의 공개 도메인 함수).참고:ggets
가지다gets
-의미론과 같은 것은 당신을 위해 뒤에 오는 새로운 선을 제거한다는 점입니다.
예, 당신은 피하고 싶습니다.gets
.fgets
버퍼가 충분히 큰 경우에는 항상 새 줄을 읽습니다(버퍼가 너무 작고 대기 중인 줄이 더 많을 때 알 수 있음).만약 당신이 원하는 것이 있다면,fgets
사용할 수 있는 새 줄을 읽지 않습니다(너무 작은 버퍼를 나타냄).fscanf
같은 합니다."%N[^\n]"
여기서 'N'은 버퍼 크기 - 1로 대체됩니다.
(이상하다면)로 후 fgets
다음과 같습니다.strtok(buffer, "\n");
이런 식이 아닙니다.strtok
사용하려고 의도한 것이지만, 저는 의도한 방식(일반적으로 피하는 방식)보다 이러한 방식으로 사용하는 경우가 더 많습니다.
이 코드에는 여러 가지 문제가 있습니다.잘못 명명된 변수와 함수를 수정하고 문제를 조사합니다.
첫째번.
CharToInt()
올른이로변합니다야경으로 을 바꿔야 합니다.StringToInt()
단일 문자가 아닌 문자열에서 작동하기 때문입니다.수
CharToInt()
[http. 통과했는지 사용자가 실수로 NULL 포인터를 통과했는지 확인하지 않습니다.입력의 유효성을 검사하지 않으며, 더 정확하게는 잘못된 입력을 건너뜁니다.사용자가 숫자가 아닌 값을 입력하면 결과에 가짜 값이 포함됩니다.에 하면,
N
전법*(s+i) & 15
14개를 생산할 것입니까!?다음으로, 설명할 수 없는 것은
temp
CharToInt()
[는 [discovery.]라고 라고 불러야 합니다.digit
그것이 진짜이기 때문에.Kulsy 한또, 그둔은.
return result / 10;
버그가 있는 구현을 해결하기에는 좋지 않은 해킹입니다.저도 마찬가지예요.
MAX
사용법과 될 수#define MAX(X,y) ((x)>(y))?(x):(y)
한 말
*(s+i)
단순히 읽을 수 있는 것만큼은 아닙니다.*s
다른 임시 인덱스를 사용하여 코드를 혼란스럽게 만들 필요가 없습니다.i
.
겟츠()
이것은 입력 문자열 버퍼를 오버플로할 수 있기 때문에 좋지 않습니다.들어 합니다.str
.
스캔f()
이것은 입력 문자열 버퍼를 오버플로할 수 있기 때문에 마찬가지로 좋지 않습니다.
"scanf() 함수를 사용할 때 첫 번째 문자의 ASCII 값이 -52이므로 결과가 완전히 잘못되었습니다."라고 언급합니다.
이는 scanf()의 잘못된 사용 때문입니다.저는 이 버그를 복제할 수 없었습니다.
페기츠
버퍼 크기(NULL 공간 포함)를 전달하여 입력 문자열 버퍼를 오버플로하지 않도록 보장할 수 있으므로 안전합니다.
줄을 서다, 줄을 보다
몇몇 사람들이 CPOSIX 표준을 제안했습니다. getline()
대신에안타깝게도 이것은 Microsoft가 C 버전을 구현하지 않기 때문에 실용적인 휴대용 솔루션이 아닙니다. 표준 C++ 문자열 템플릿만 이 SO #27755191 질문 답변으로 작동합니다.마이크로소프트의 C++getline()
적어도 Visual Studio 6에서 사용할 수 있었지만 OP가 C++가 아닌 C에 대해 엄격하게 질문하고 있기 때문에 이것은 옵션이 아닙니다.
미씨.
마지막으로, 이 구현은 정수 오버플로를 감지하지 못한다는 점에서 버그가 있습니다.를 너무 입력하면 될수! 즉, 사가숫자너많입숫음될수수있다습니가!9876543210
될것다니입이 될 입니다.-18815698
그것도 고쳐요.
이 문제는 해결하기에 사소한 것입니다.unsigned int
이전 부분 번호가 현재 부분 번호보다 작으면 오버플로가 발생하여 이전 부분 번호를 반환합니다.
당분간signed int
이것은 조금 더 많은 일입니다.어셈블리에서 우리는 캐리 플래그를 검사할 수 있지만 C에는 서명된 int 산술로 오버플로를 감지하는 표준 내장 방법이 없습니다.를 곱하고 있기 에, 다히도상곱, 수를하있때기에문고는우리행,▁fortun때에,* 10
동등한 방정식을 사용하면 이를 쉽게 감지할 수 있습니다.
n = x*10 = x*8 + x*2
x*8이 오버플로우되면 논리적으로 x*10도 오버플로우됩니다.32비트 int 오버플로는 x*8 = 0x100000000일 때 발생하므로 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;
}
절대로 사용해서는 안 된다는 당신의 말이 맞습니다.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입니다.
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
및 될 수 있는 하며, 을 낭비하지 않아도 됩니다.strlen
은 " 를사수없다니습용할어레이다▁with"와 함께 수 .getline
포인터는 다음과 같아야 합니다.NULL
또는 자유롭게 사용할 수 있습니다.
당신이 어떤 문제를 가지고 있는지 잘 모르겠습니다.scanf
.
gets를 사용하지 않음으로 인해 예측할 수 없는 오버플로가 발생할 수 있습니다.만약 당신의 문자열 배열의 크기가 1000이고 내가 1001자를 입력한다면, 나는 당신의 프로그램을 버퍼 오버플로 할 수 있습니다.
CharToInt()의 수정된 버전과 함께 fget()를 사용해 보십시오.
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;
}
기본적으로 입력 숫자를 확인하고 다른 항목은 무시합니다.이것은 매우 조잡하니 맛에 맞게 소금과 그것을 수정하세요.
그래서 저는 대단한 프로그래머는 아니지만, 당신의 질문에 대답해 보겠습니다.scanf();
스캔은 꽤 괜찮은 것 같고 거의 모든 것에 문제없이 사용합니다.하지만 당신은 완전히 정확하지 않은 구조를 취했습니다.다음과 같아야 합니다.
char str[MAX];
printf("Enter some text: ");
scanf("%s", &str);
fflush(stdin);
변수 앞의 "&"은 중요합니다.스캔한 값을 저장할 위치(변수)를 프로그램에 알려줍니다.그fflush(stdin);
표준 입력(임시)에서 버퍼를 삭제하여 버퍼 오버플로를 발생시킬 가능성이 줄어듭니다.
get은 get/scanf가 fgets라는 것입니다.gets();
그리고.scanf();
만 가능합니다.' '
하는 동안에fgets();
입력을 ( 나중에.)
언급URL : https://stackoverflow.com/questions/3302255/c-scanf-vs-gets-vs-fgets
'prosource' 카테고리의 다른 글
장고: 로그인 후 이전 페이지로 리디렉션 (0) | 2023.06.17 |
---|---|
Typescript 및 Jest를 사용한 단순 가져오기 모의 (0) | 2023.06.17 |
VBA, 문자열에 특정 문자가 포함된 경우 (0) | 2023.06.12 |
개별 Python 장치 테스트를 일시적으로 사용하지 않도록 설정 (0) | 2023.06.12 |
Angular 5 - 브라우저 새로 고침 시 홈페이지로 페이지 리디렉션 (0) | 2023.06.12 |