문제

단점을 알고 싶습니다. scanf().

많은 사이트에서 나는 다음을 사용하여 그것을 읽었습니다. scanf 버퍼 오버플로가 발생할 수 있습니다.그 이유는 무엇입니까?다른 단점이 있나요? scanf?

도움이 되었습니까?

해결책

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

  • 사용하여 %s 사용자로부터 문자열을 얻으려면 문자열이 버퍼보다 ​​길어 오버플로가 발생할 가능성이 있습니다.
  • 스캔이 실패하여 파일 포인터가 불확실한 위치에 남아 있을 가능성이 있습니다.

나는 사용하는 것을 매우 선호합니다 fgets 읽는 데이터의 양을 제한할 수 있도록 전체 줄을 읽습니다.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;

    // 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.
    size_t lastPos = strlen(buff) - 1;
    if (buff[lastPos] != '\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[lastPos] = '\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;
}

마지막으로 실제로 작동하는 모습을 보여주기 위해 테스트를 실행합니다.

$ ./tstprg
Enter string>[CTRL-D]
No input

$ ./tstprg
Enter string> a
OK [a]

$ ./tstprg
Enter string> hello
OK [hello]

$ ./tstprg
Enter string> hello there
Input too long [hello the]

$ ./tstprg
Enter string> i am pax
OK [i am pax]

다른 팁

지금까지 대부분의 답변은 문자열 버퍼 오버 플로우 문제에 중점을 둔 것 같습니다. 실제로, 사용할 수있는 형식 지정자 scanf 기능은 명시 적으로 지원합니다 필드 너비 입력의 최대 크기를 제한하고 버퍼 오버플로를 방지하는 설정. 이것은 존재하는 String-Buffer Overflow 위험에 대한 대중적인 비난을 만듭니다. scanf 사실상 근거가 없습니다. 그것을 주장합니다 scanf 어떻게 든 유사합니다 gets 그 점에서 완전히 틀 렸습니다. 사이에는 큰 질적 차이가 있습니다 scanf 그리고 gets: scanf 사용자에게 String-Buffer-Overflow Preventing 기능을 제공합니다 gets 그렇지 않습니다.

이것들은 이것을 주장 할 수 있습니다 scanf 필드 너비는 형식 문자열에 포함되어야하기 때문에 기능을 사용하기가 어렵습니다 ( printf). 그것은 실제로 사실입니다. scanf 실제로 그 점에서 다소 제대로 설계되었습니다. 그럼에도 불구하고 어떤 주장도 scanf String-Buffer-Overflow 안전성에 대해 어떻게 든 절망적으로 깨졌습니다. 안전은 완전히 가짜이며 일반적으로 게으른 프로그래머에 의해 만들어졌습니다.

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

따라서 위의 내용을 요약하기 위해 scanf 문자열 버퍼와 함께 올바르게 안전하게 사용하는 것은 어렵다는 것입니다. 산술 입력에 안전하게 사용하는 것은 불가능합니다. 후자는 실제 문제입니다. 전자는 단지 불편한 일입니다.

추신 위의 내용은 온 가족에 관한 것입니다. scanf 기능 (또한 포함 fscanf 그리고 sscanf). 와 함께 scanf 구체적으로, 명백한 문제는 잠재적으로 읽을 수있는 엄격한 형식의 기능을 사용한다는 아이디어가 인터렉티브 입력은 다소 의문의 여지가 있습니다.

comp.lang.c faq에서 : 모두가 Scanf를 사용하지 말라고하는 이유는 무엇입니까? 대신 무엇을 사용해야합니까?

scanf 질문을 참조하십시오 12.17, 12.18a, 그리고 12.19. 또한, 그것 %s 형식은 같은 문제가 있습니다 gets() (질문 참조 12.23) - 수신 버퍼가 오버플로되지 않도록 보장하기가 어렵습니다. 각주

더 일반적으로, scanf 비교적 구조화 된 형식 입력을 위해 설계되었습니다 (이름은 실제로 "스캔 형식"에서 파생된다). 주의를 기울이면 성공했는지 실패했는지 여부를 알려주지 만 대략적으로 실패한 위치 만 말해 줄 수 있습니다. 오류 복구를 할 기회가 거의 없습니다.

그러나 대화식 사용자 입력은 가장 잘 구조화 된 입력입니다. 잘 설계된 사용자 인터페이스는 사용자가 숫자가 예상 될 때 문자 나 문장 부호뿐만 아니라 예상보다 더 많은 문자 또는 전혀 문자가없는 (문자도 더 적은 문자)를 입력 할 가능성을 허용합니다., 단지 반환 키) 또는 조기 EOF 또는 무엇이든. 사용할 때 이러한 모든 잠재적 문제를 우아하게 다루는 것은 거의 불가능합니다. scanf; 전체 라인을 읽는 것이 훨씬 쉽습니다 ( fgets 또는 같은), 다음을 사용하여 해석하십시오. sscanf 또는 다른 기술. (기능과 같은 기능 strtol, strtok, 그리고 atoi 종종 유용합니다. 질문도 참조하십시오 12.16 그리고 13.6.) 사용하는 경우 scanf 변형, 반환 값을 확인하여 예상 품목 수가 발견되었는지 확인하십시오. 또한 사용하는 경우 %s, 버퍼 오버플로를 보호하십시오.

그건 그렇고, 그 비판에 주목하십시오 scanf 반드시 기소 할 필요는 없습니다 fscanf 그리고 sscanf. scanf 읽습니다 stdin, 이는 일반적으로 대화식 키보드이므로 제한이 가장 적어 가장 큰 문제로 이어집니다. 반면에 데이터 파일에 알려진 형식이있는 경우 fscanf. 문자열을 구문 분석하는 것은 완벽하게 적절합니다 sscanf (반환 값을 확인하는 한) 제어를 되찾기가 쉽고 스캔을 다시 시작하고 일치하지 않으면 입력을 폐기하는 등.

추가 링크 :

참고 문헌 : K & R2 Sec. 7.4 p. 159

얻기가 매우 어렵습니다 scanf 당신이 원하는 일을하기 위해. 물론, 당신은 할 수 있습니다 scanf("%s", buf); 위험합니다 gets(buf);, 모두가 말했듯이.

예를 들어, Paxdiablo가 자신의 기능에서 읽을 수있는 일은 다음과 같이 수행 할 수 있습니다.

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

위의 내용은 줄을 읽고, 처음 10 개의 비 뉴 라인 문자를 저장합니다. buf, 그런 다음 모든 것을 신축 할 때까지 (및 포함) 버립니다. 따라서 Paxdiablo의 기능은 사용하여 작성 될 수 있습니다 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 가족(scanf,sscanf, fscanf..etc) esp 문자열을 읽을 때 버퍼의 길이 (읽기 중)를 고려하지 않기 때문에.

예시:

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

분명히 버퍼 buf Max를 잡을 수 있습니다 3 숯. 하지만 sscanf 넣으려고 노력할 것입니다 "abcdef" 버퍼 오버플로를 유발합니다.

내가 가진 문제 *scanf() 가족:

  • %S 및 %[변환 지정자로 버퍼 오버 플로우 가능성. 예, 최대 필드 너비를 지정할 수 있지만 printf(), 당신은 그것을 논쟁으로 만들 수 없습니다 scanf() 전화; 변환 지정자에 하드 코딩되어야합니다.
  • %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- 기능 - 부족 어느 타입 안전. 즉, 코딩 할 수 있습니다.

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

지옥,조차도 "괜찮아"입니다.

scanf("%10s", i);

그것은보다 나쁩니다 printf-기능과 같은 기능 scanf 포인터가 예상되므로 충돌이 더 가능해집니다.

물론, 일부 형식 스펙 시퍼 체커가 있지만, 완벽하지는 않지만 언어 나 표준 라이브러리의 일부는 아닙니다.

장점 scanf C에서 항상 해야 하는 것처럼 도구 사용 방법을 배우면 매우 유용한 사용 사례가 있습니다. 사용법을 배울 수 있습니다. scanf 읽고 이해함으로써 친구들과 매뉴얼.심각한 이해 문제 없이 해당 매뉴얼을 읽을 수 없다면 이는 아마도 C를 잘 모른다는 의미일 것입니다.


scanf 그리고 친구들은 불행한 디자인 선택으로 어려움을 겪었습니다. 다른 답변에서 알 수 있듯이 문서를 읽지 않고 올바르게 사용하기가 어렵고 때로는 불가능합니다.불행하게도 이 문제는 C 전체에서 발생하므로 사용하지 말라고 조언한다면 scanf 그렇다면 아마도 C를 사용하지 않는 것이 좋습니다.

가장 큰 단점 중 하나는 순전히 초보자들 사이에서 얻은 평판인 것 같습니다.;C의 많은 유용한 기능과 마찬가지로 C를 사용하기 전에 충분한 정보를 얻어야 합니다.핵심은 C의 나머지 부분과 마찬가지로 간결하고 관용적으로 보이지만 미묘하게 오해의 소지가 있을 수 있다는 점을 깨닫는 것입니다.이는 C에 널리 퍼져 있습니다.초보자가 자신이 생각하기에 합리적이라고 생각하고 처음에는 작동할 수도 있지만 말이 되지 않고 치명적으로 실패할 수 있는 코드를 작성하는 것은 쉽습니다.

예를 들어, 경험이 없는 사람들은 일반적으로 %s 대리인이 원인이 될 것입니다 한 줄 직관적으로 보일 수도 있지만 반드시 그런 것은 아닙니다.읽은 필드를 다음과 같이 설명하는 것이 더 적절합니다. 단어.모든 기능에 대해 설명서를 읽는 것이 좋습니다.

이 질문에 대한 안전성 부족과 버퍼 오버플로 위험을 언급하지 않고 어떻게 대답하겠습니까?우리가 이미 다루었듯이 C는 안전한 언어가 아니며, 우리가 정확성을 희생하면서 최적화를 적용하거나 게으른 프로그래머이기 때문에 최적화를 적용할 수 있게 해줍니다.따라서 시스템이 고정된 바이트 수보다 큰 문자열을 절대 수신하지 않는다는 것을 알면 크기를 확인하고 범위 검사를 생략하는 배열을 선언할 수 있는 기능이 제공됩니다.나는 이것이 몰락이라고 생각하지 않습니다.그것은 옵션입니다.다시 한번 말씀드리지만, 매뉴얼을 읽어보시면 이 옵션을 알 수 있을 것입니다.

게으른 프로그래머들만이 문제가 되는 것은 아닙니다. scanf.책을 읽으려고 노력하는 사람들을 보는 것은 드문 일이 아닙니다. float 또는 double 다음을 사용하여 값 %d, 예를 들어.그들은 일반적으로 구현이 배후에서 일종의 변환을 수행할 것이라고 잘못 생각합니다. 이는 나머지 언어 전체에서 유사한 변환이 발생하기 때문에 의미가 있지만 여기서는 그렇지 않습니다.앞서 말했듯이, scanf 친구들(그리고 실제로 C의 나머지 부분)은 기만적입니다.간결하고 관용적인 것처럼 보이지만 그렇지 않습니다.

경험이 부족한 프로그래머는 작업의 성공 여부를 고려하지 않아도 됩니다..우리가 지시했을 때 사용자가 완전히 숫자가 아닌 것을 입력했다고 가정해 보세요. scanf 다음을 사용하여 일련의 십진수를 읽고 변환합니다. %d.이러한 잘못된 데이터를 가로챌 수 있는 유일한 방법은 반환 값을 확인하는 것인데, 우리는 얼마나 자주 반환 값을 확인하려고 애쓰나요?

매우 좋아 fgets, 언제 scanf 친구들이 읽도록 지시받은 내용을 읽지 못하면 스트림이 비정상적인 상태로 남게 됩니다.- 다음의 경우 fgets, 전체 줄을 저장할 공간이 충분하지 않은 경우 읽지 않은 채로 남아 있는 줄의 나머지 부분은 그렇지 않은 경우 새 줄인 것처럼 잘못 처리될 수 있습니다.- 다음의 경우 scanf 친구 여러분, 위에 설명된 대로 변환이 실패하면 잘못된 데이터가 스트림에서 읽히지 않은 채로 남아 있으며 마치 다른 필드의 일부인 것처럼 잘못 처리될 수 있습니다.

사용하기가 더 쉽지 않습니다 scanf 친구와 함께 사용하는 것보다 fgets.우리가 다음을 찾아서 성공을 확인한다면 '\n' 우리가 사용할 때 fgets 또는 사용할 때 반환 값을 검사하여 scanf 친구들과 함께, 우리는 다음을 사용하여 불완전한 줄을 읽었음을 발견했습니다. fgets 또는 다음을 사용하여 필드를 읽지 못했습니다. scanf, 그러면 우리는 동일한 현실에 직면하게 됩니다.우리는 아마도 입력 무시 (보통 다음 개행 문자까지 포함)!유우우우우우우우!

안타깝게도, scanf 둘 다 동시에 이러한 방식으로 입력을 삭제하는 것을 어렵고(비직관적) 쉽게(최소 키 입력) 만듭니다.사용자 입력을 폐기하는 현실에 직면하여 일부는 다음을 시도했습니다. scanf("%*[^\n]%*c");, 그 사실을 깨닫지 못하고 %*[^\n] 개행만 만나면 델리게이트는 실패하므로 개행은 여전히 ​​스트림에 남아 있습니다.

두 형식 대리자를 분리하여 약간의 조정을 수행하면 여기서 약간의 성공을 볼 수 있습니다. scanf("%*[^\n]"); getchar();.다른 도구를 사용하여 몇 번의 키 입력으로 시도해 보세요. ;)

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top