prosource

C - scanf() vs gets() vs fgets()

probook 2023. 6. 17. 09:26
반응형

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대신에, 때때로 불편하기는 하지만 (새 줄을 벗기고, 버퍼 크기를 미리 결정해야 하며, 너무 긴 줄을 어떻게 처리해야 하는지를 파악해야 합니다.) 읽었던 부분을 유지하고 초과 부분을 폐기하고 전체를 폐기하고 버퍼를 동적으로 확장하고 다시 시도하는 등의 작업을 수행해야 합니다.이러한 동적 할당을 수행하는 일부 비표준 함수(예:getlinePOSIX 시스템에서 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) & 1514개를 생산할 것입니까!?

  • 다음으로, 설명할 수 없는 것은tempCharToInt()[는 [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

반응형