programing

scanf의 단점

luckcodes 2022. 7. 19. 22:18

scanf의 단점

의 단점을 알고 싶다scanf().

많은 사이트에서는, 다음과 같이 읽습니다.scanf버퍼 오버플로의 원인이 될 수 있습니다.그 이유는 무엇입니까?에는 다른 결점이 있습니까?scanf?

지금까지의 답변의 대부분은 문자열 버퍼 오버플로 문제에 초점을 맞추고 있는 것 같습니다.실제로 와 함께 사용할 수 있는 형식 지정자scanf함수는 입력의 최대 크기를 제한하고 버퍼 오버플로를 방지하는 명시적 필드 폭 설정을 지원합니다.이로 인해 에 존재하는 스트링 버퍼 오버플로 위험에 대한 일반적인 비난이 제기됩니다.scanf사실 무근의라고 주장하다scanf어쩐지 와 유사하다gets완전히 틀렸습니다에는 큰 질적 차이가 있다scanf그리고.gets:scanf는, 유저에게 스트링 스위칭 기능을 제공하고 있습니다만,getsDoesn't.

이런 것들이라고 주장할 수 있다.scanf필드 너비를 형식 문자열에 삽입해야 하기 때문에 기능을 사용하기 어렵습니다(변수 인수를 통해 필드 너비를 전달할 방법은 없습니다).printf그건 사실이에요. scanf그런 점에서 꽤 형편없이 설계되어 있습니다.하지만 그럼에도 불구하고scanf스트링 스트링 스트링의 안전성에 관해 어찌된 일인지 절망적으로 고장난 것은 완전히 가짜이며 보통 게으른 프로그래머들에 의해 만들어진다.

진짜 문제점은scanf전혀 다른 성질을 가지고 있습니다.그것도 오버플로우입니다.언제scanf함수는 숫자의 십진수 표현을 산술 유형의 값으로 변환하는 데 사용되며 산술 오버플로로부터 보호되지 않습니다.오버플로가 발생하면scanf정의되지 않은 동작을 생성합니다.이러한 이유로 C 표준 라이브러리에서 변환을 수행하는 유일한 적절한 방법은 functions입니다.strto...가족.

위의 문제를 요약하면,scanf스트링 버퍼에서는 적절하고 안전하게 사용하기 어렵다는 것(가능성이 거의 없음)입니다.그리고 산술 입력에 안전하게 사용하는 것은 불가능하다.후자가 진짜 문제다.전자는 단지 불편할 뿐이다.

추신. 위의 내용은 의 전체 가족에 대한 것입니다.scanf기능(또한 포함)fscanf그리고.sscanf)와 함께scanf특히, 분명한 문제는 잠재적으로 인터랙티브한 입력을 읽기 위해 엄격하게 정의된 함수를 사용하는 바로 그 생각이 다소 의심스럽다는 것입니다.

scanf의 문제는 (최소한) 다음과 같습니다.

  • 를 사용합니다.%s사용자로부터 문자열을 가져오면 해당 문자열이 버퍼보다 길어 오버플로가 발생할 수 있습니다.
  • 검색 실패 시 파일 포인터가 불확실한 위치에 남겨질 수 있습니다.

저는 이 제품을 사용하는 것을 매우 선호합니다.fgets전체 행을 읽어서 데이터 읽기 양을 제한할 수 있습니다.버퍼가 1K 버퍼가 있는 들일 , 1K 버퍼는 1K 버퍼로 되어 .fgets줄 바꿈 문자가 없는 경우(줄 바꿈이 없는 파일의 마지막 줄)로 줄이 너무 길지 않은지 알 수 있습니다.

그런 다음 사용자에게 불만을 제기하거나 회선의 나머지 부분에 더 많은 공간을 할당할 수 있습니다(필요한 경우 충분한 공간이 확보될 때까지 계속).어느 경우든 버퍼 오버플로의 위험은 없습니다.

행을 읽으면 다음 행에 배치되어 있다는 것을 알 수 있으므로 문제 없습니다.그럼 할 수 있어요sscanf파일을 다시 읽기 위해 파일 포인터를 저장 및 복원할 필요 없이 문자열을 마음껏 사용할 수 있습니다.

다음은 사용자에게 정보를 요청할 때 버퍼 오버플로를 방지하기 위해 자주 사용하는 코드 조각입니다.

필요에 따라서, 표준 입력 이외의 파일을 사용하도록 간단하게 조정할 수 있습니다.또, 버퍼를 자신의 버퍼에 할당(및 버퍼의 사이즈가 커질 때까지 계속 증가)한 후, 발신자에게 반환할 수도 있습니다(물론, 발신자가 버퍼를 해방할 책임이 있습니다).

#include <stdio.h>
#include <string.h>

#define OK         0
#define NO_INPUT   1
#define TOO_LONG   2
#define SMALL_BUFF 3
static int getLine (char *prmpt, char *buff, size_t sz) {
    int ch, extra;

    // Size zero or one cannot store enough, so don't even
    // try - we need space for at least newline and terminator.

    if (sz < 2)
        return SMALL_BUFF;

    // Output prompt.

    if (prmpt != NULL) {
        printf ("%s", prmpt);
        fflush (stdout);
    }

    // Get line with buffer overrun protection.

    if (fgets (buff, sz, stdin) == NULL)
        return NO_INPUT;

    // Catch possibility of `\0` in the input stream.

    size_t len = strlen(buff);
    if (len < 1)
        return NO_INPUT;

    // If it was too long, there'll be no newline. In that case, we flush
    // to end of line so that excess doesn't affect the next call.

    if (buff[len - 1] != '\n') {
        extra = 0;
        while (((ch = getchar()) != '\n') && (ch != EOF))
            extra = 1;
        return (extra == 1) ? TOO_LONG : OK;
    }

    // Otherwise remove newline and give string back to caller.
    buff[len - 1] = '\0';
    return OK;
}

테스트 드라이버:

// Test program for getLine().

int main (void) {
    int rc;
    char buff[10];

    rc = getLine ("Enter string> ", buff, sizeof(buff));
    if (rc == NO_INPUT) {
        // Extra NL since my system doesn't output that on EOF.
        printf ("\nNo input\n");
        return 1;
    }

    if (rc == TOO_LONG) {
        printf ("Input too long [%s]\n", buff);
        return 1;
    }

    printf ("OK [%s]\n", buff);

    return 0;
}

마지막으로 실제 동작을 보여주는 테스트 실행:

$ printf "\0" | ./tstprg     # Singular NUL in input stream.
Enter string>
No input

$ ./tstprg < /dev/null       # EOF in input stream.
Enter string>
No input

$ ./tstprg                   # A one-character string.
Enter string> a
OK [a]

$ ./tstprg                   # Longer string but still able to fit.
Enter string> hello
OK [hello]

$ ./tstprg                   # Too long for buffer.
Enter string> hello there
Input too long [hello the]

$ ./tstprg                   # Test limit of buffer.
Enter string> 123456789
OK [123456789]

$ ./tstprg                   # Test just over limit.
Enter string> 1234567890
Input too long [123456789]

comp.lang.c FAQ: 왜 다들 scanf를 사용하지 말라고 하죠? 대신 무엇을 사용하면 좋을까요?

scanf에는 몇 가지 문제가 있습니다.질문 12.17, 12.18a12.19를 참조해 주십시오.또한, 그것은%s 것은, 포맷에 문제가 .gets()has (질문 12.23) : 수신 버퍼가 오버플로하지 않는다고 보증하는 것은 어렵습니다.[아쉬움]

일반적으로는, 「 」입니다.scanf는 비교적 구조화된 형식화된 입력용으로 설계되어 있습니다(이 이름은 실제로는 "스캔 형식"에서 파생되었습니다).주의를 기울이면 성공 또는 실패 여부를 알 수 있지만 실패의 대략적인 위치만 알 수 있으며 방법이나 이유는 전혀 알 수 없습니다.에러 회복을 실시할 기회는 거의 없습니다.

그러나 대화형 사용자 입력은 가장 구조화되지 않은 입력입니다.잘 설계된 사용자 인터페이스에서는 사용자가 숫자를 예상했을 때 문자나 구두점뿐만 아니라 예상보다 많거나 적거나 문자가 전혀 없거나(RETURN 키만), 조기 EOF 등 거의 모든 것을 입력할 수 있습니다.이 모든 잠재적인 문제에 적절하게 대처하는 것은 거의 불가능합니다.scanf요.fgets을 사용하여 합니다.sscanf (은 '하다'와 같은 것).strtol,strtok , , , , 입니다.atoi도움이 되는 경우가 많습니다.질문 12.1613.6도 참조해 주세요).사용하시는 경우scanfvariant,하십시오.반환: " " " " " 를 반환하십시오.또, 「」를 사용하는 는, 「」를 합니다.%s버퍼 오버플로를 방지해 주세요.

참고로, 이 비판은scanf반드시 기소되는 것은 아니다fscanf그리고.sscanf.scanf에서 읽다stdin이 키보드는 보통 인터랙티브키보드이기 때문에 가장 구속이 적고 대부분의 문제를 일으킵니다.한편, 데이터 파일이 이미 알려진 형식을 가지고 있는 경우, 다음과 같이 읽는 것이 적절할 수 있습니다.fscanf스트링을 해석하는 것은 매우 적절합니다.sscanf(반환값이 체크되어 있는 한) 제어권 회복, 스캔 재시작, 일치하지 않을 경우 입력 파기 등이 매우 용이하기 때문입니다.

기타 링크:

참고 자료: K&R2 제7.4장 (159쪽)

의 장점은 C에서 항상 해야 하는 것처럼 툴의 사용법을 익히면 매우 유용한 사용 사례를 얻을 수 있다는 것입니다.사용법을 배울 수 있습니다.scanf매뉴얼을 읽고 이해함으로써 친구를 사귀게 됩니다.만약 당신이 심각한 이해 문제 없이 그 매뉴얼을 읽을 수 없다면, 이것은 당신이 C를 잘 모른다는 것을 의미할 수 있습니다.


scanf 그리고 다른 답변에서 보듯이 설명서를 읽지 않으면 올바르게 사용하기 어려워지는(때로는 불가능할 수 있는) 설계 선택에 시달리는 친구들도 있었습니다.이것은 불행히도 C 전체에서 발생하고 있기 때문에, 만약 내가 그것을 사용하지 말 것을 조언한다면.scanfC를 사용하지 말 것을 권하고 싶습니다.

가장 결점하나는 C의 많은 유용한 기능과 마찬가지로 사용하기 전에 충분한 정보를 얻을 필요가 있다는 것입니다.중요한 것은 C의 나머지 부분과 마찬가지로 간결하고 관용적인 것처럼 보이지만 미묘하게 오해를 일으킬 수 있다는 것을 깨닫는 것이다.이것은 C에 널리 퍼져 있습니다.초보자라면 이치에 맞다고 생각되는 코드를 쉽게 작성할 수 있으며 처음에는 도움이 될 수도 있지만 앞뒤가 맞지 않아 치명적인 장애가 발생할 수 있습니다.

예를 들어, 미개시자는 일반적으로 다음과 같이 예상합니다.%s대리인은 을 읽을 수 있으며, 직관적으로 보일 수 있지만 반드시 사실인 것은 아닙니다.읽히는 현장을 단어로 표현하는 것이 더 적절합니다.모든 기능에 대해 설명서를 읽는 것이 좋습니다.

안전성의 결여와 버퍼 오버플로우의 위험을 언급하지 않고 이 질문에 대한 어떤 답변이 있을까요?이미 설명한 바와 같이 C는 안전한 언어가 아니기 때문에 정확성을 희생하거나 게으른 프로그래머이기 때문에 최적화를 적용할 수 있습니다.따라서 시스템이 고정 바이트 수보다 큰 문자열을 수신하지 않는다는 것을 알게 되면 크기 조정 및 경계 검사 포기를 선언할 수 있습니다.나는 이것을 추락이라고 생각하지 않는다.그것은 옵션이다.다시 한 번 매뉴얼을 읽어보시는 것이 좋습니다.이 옵션이 표시됩니다.

게으른 프로그래머들만이 시달리는 것은 아니다.책을 읽으려는 사람들을 흔히 볼 수 있다.float또는double값 사용%d,예를들면.그들은 보통 구현이 백그라운드에서 어떤 변환 작업을 수행한다고 착각하고 있습니다. 이는 다른 언어 전반에 걸쳐 유사한 변환이 이루어지기 때문에 타당합니다. 하지만 여기서는 그렇지 않습니다.아까도 말씀드렸듯이scanf그리고 친구(그리고 C의 나머지)는 기만적이다; 그들은 간결하고 관용적으로 보이지만 그렇지 않다.

경험이 부족한 프로그래머가 작업의 성공을 고려하도록 강요받지 않습니다.사용자가 숫자 이외의 정보를 입력했다고 가정해 봅시다.scanf십진수를 읽고 변환하다%d이렇게 잘못된 데이터를 가로채는 유일한 방법은 반환값을 확인하는 것인데, 반환값을 확인하는 데 얼마나 자주 신경을 써야 합니까?

마치, 친구들이 읽으라고 것을 읽지 못할 때처럼, 스트림은 비정상적인 상태로 남게 됩니다; - 의 경우fgets완전한 행을 저장하기에 충분한 공간이 없는 경우 읽지 않은 나머지 행은 새 행인 것처럼 잘못 처리될 수 있습니다.- 이 경우scanf그리고 위에서 설명한 바와 같이 변환에 실패하면 잘못된 데이터가 스트림에서 읽지 않은 상태로 남아 다른 필드의 일부인 것처럼 잘못 취급될 수 있습니다.

친구나 친구를 사용하는 것보다 사용하는 것이 더 쉽지는 않다.성공 여부를 확인하기 위해'\n'사용할 때fgets또는 사용 시 반환값을 검사하여scanf그리고 우리는 우리가 불완전한 대사를 읽었다는 것을 알게 되었다fgets또는 를 사용하여 필드를 읽지 못했습니다.scanf그렇다면 우리는 같은 현실에 직면하게 됩니다.입력(보통 다음 줄까지 포함)은 폐기될 수 있습니다.유우욱!

불행하게도,scanf이러한 방식으로 입력을 폐기하는 것이 (직관적이지 않은) 동시에 (가장 간단한 키 입력) 어려워집니다.이러한 사용자 입력의 폐기의 현실에 직면하여 일부에서는 scanf("%*[^\n]%*c"); 하지만, 그 사실이%*[^\n]새 회선만 발견되면 대리인이 실패하므로 새 회선은 여전히 스트림에 남아 있습니다.

두 명의 형식 딜러를 분리하여 약간 조정하면 다음과 같은 성공이 있습니다.scanf("%*[^\n]"); getchar();. 다른 툴을 사용하여 키 입력을 거의 하지 않고 실행해 보십시오.

에 관한 문제*scanf()패밀리:

  • %s 및 %[ 변환 지정자가 있는 버퍼 오버플로우 가능성이 있습니다.예, 최대 필드 너비를 지정할 수 있지만printf()에서는 그것을 논쟁으로 삼을 수 없습니다.scanf()call. 변환 지정자로 하드코드해야 합니다.
  • %d, %i 등의 산술 오버플로우 가능성
  • 잘못된 형식의 입력을 감지하고 거부하는 기능이 제한됩니다.예를 들어 "12w4"는 유효한 정수는 아니지만scanf("%d", &value);정상적으로 변환되어 12가 할당됩니다.value입력 스트림에 w4를 꽂아두면 나중에 읽게 됩니다.입력 문자열 전체가 거부되는 것이 이상적이지만,scanf()그렇게 할 수 있는 쉬운 메커니즘이 없습니다.

입력이 항상 오버플로에 영향을 주지 않는 고정 길이의 문자열과 수치로 올바르게 형성된다는 것을 알고 있다면,scanf()훌륭한 도구입니다.인터랙티브 입력 또는 올바른 형식이 보장되지 않는 입력을 취급하는 경우 다른 방법을 사용하십시오.

여기에서는, 사용의 잠재적인 오버플로우 문제에 대해 많은 회답이 설명되고 있습니다.scanf("%s", buf)단, 최신 POSIX 사양에서는 이 문제를 해결합니다.m형식 지정자에 사용할 수 있는 할당 할당 문자c,s,그리고.[포맷을 지정합니다.이렇게 하면scanf필요한 만큼 메모리를 할당하다malloc(따라서 나중에 를 사용하여 해방되어야 합니다.free).

사용 예:

char *buf;
scanf("%ms", &buf); // with 'm', scanf expects a pointer to pointer to char.

// use buf

free(buf);

여기 보세요.이 방법의 단점은 POSIX 사양에 비교적 최근에 추가된 것으로 C 사양에 전혀 명시되어 있지 않기 때문에 현시점에서는 휴대할 수 없다는 것입니다.

구하기가 매우 어렵다scanf네가 원하는 걸 할 수 있어물론 할 수 있지, 하지만...scanf("%s", buf);만큼이나 위험하다gets(buf);모두가 말했듯이.

예를 들어, 팍스디아블로는 다음과 같은 기능을 통해 읽을 수 있습니다.

scanf("%10[^\n]%*[^\n]", buf));
getchar();

위에서는 행을 읽고 첫 번째 10개의 줄 바꿈 이외의 문자를 저장합니다.buf새로운 행이 될 때까지(포함) 모든 것을 파기합니다.따라서 팍스디아블로의 함수는 다음과 같이 쓸 수 있다.scanf다음과 같이 합니다.

#include <stdio.h>

enum read_status {
    OK,
    NO_INPUT,
    TOO_LONG
};

static int get_line(const char *prompt, char *buf, size_t sz)
{
    char fmt[40];
    int i;
    int nscanned;

    printf("%s", prompt);
    fflush(stdout);

    sprintf(fmt, "%%%zu[^\n]%%*[^\n]%%n", sz-1);
    /* read at most sz-1 characters on, discarding the rest */
    i = scanf(fmt, buf, &nscanned);
    if (i > 0) {
        getchar();
        if (nscanned >= sz) {
            return TOO_LONG;
        } else {
            return OK;
        }
    } else {
        return NO_INPUT;
    }
}

int main(void)
{
    char buf[10+1];
    int rc;

    while ((rc = get_line("Enter string> ", buf, sizeof buf)) != NO_INPUT) {
        if (rc == TOO_LONG) {
            printf("Input too long: ");
        }
        printf("->%s<-\n", buf);
    }
    return 0;
}

다른 문제 중 하나는scanf오버플로우 시의 동작입니다.예를 들어, 다음을 읽을 때int:

int i;
scanf("%d", &i);

오버플로우 발생 시 위 내용을 안전하게 사용할 수 없습니다.첫 번째 경우에도 문자열을 읽는 것이 훨씬 더 간단하다.fgets가 아니라scanf.

에는 큰 문제가 하나 있습니다.scanf- 같은 기능 - 어떤 유형의 안전성이 결여되어 있습니다.즉, 다음을 코드화할 수 있습니다.

int i;
scanf("%10s", &i);

이것마저도 괜찮아

scanf("%10s", i);

보다 더 나쁘다printf-기능을 좋아합니다.왜냐하면scanf포인터를 기대하기 때문에 크래시가 발생할 가능성이 높아집니다.

물론, 일부 형식 지정 체커가 있지만, 그것들은 완벽하지 않고 언어나 표준 라이브러리의 일부가 아닙니다.

네, 맞아요.에는 중대한 보안 결함이 있습니다.scanf패밀리(scanf,sscanf,fscanf특히 문자열을 읽을 때 버퍼의 길이(읽고 있는 길이)를 고려하지 않기 때문입니다.

예:

char buf[3];
sscanf("abcdef","%s",buf);

완충재를 명확히 하다bufMAX 유지 가능3char. 근데 그...sscanf넣으려고 할 것이다"abcdef"버퍼 오버플로를 일으킵니다.

언급URL : https://stackoverflow.com/questions/2430303/disadvantages-of-scanf