Nice programing

scanf의 단점

nicepro 2020. 12. 10. 21:07
반응형

scanf의 단점


의 단점을 알고 싶습니다 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함수 와 함께 사용할 수있는 형식 지정 자는 입력의 최대 크기를 제한하고 버퍼 오버플로를 방지하는 명시 적 필드 너비 설정을 지원 합니다. 이것은 scanf사실상 근거없는 곳에 존재하는 문자열 버퍼 오버 플로우 위험에 대한 대중적인 비난을 렌더링합니다 . 그 점에서 scanf유사하다고 주장하는 것은 gets완전히 잘못된 것입니다. 사이의 주요 질적 인 차이가있다 scanfgets: scanf반면, 문자열 버퍼 오버 플로우 방지 기능을 사용자에게 제공합니까 gets하지 않습니다.

scanf필드 너비가 형식 문자열에 포함되어야하기 때문에 이러한 기능을 사용하기 어렵다고 주장 할 수 있습니다 (에서 수행 할 수있는 가변 인수를 통해 전달할 방법이 없습니다 printf). 그것은 사실입니다. scanf실제로 그 점에서 다소 잘못 설계되었습니다. 그러나 그럼에도 불구하고 scanf문자열 버퍼 오버플로 안전과 관련하여 어떻게 든 절망적으로 깨지는 주장 은 완전히 가짜이며 일반적으로 게으른 프로그래머에 의해 만들어집니다.

의 실제 문제 scanf오버플로 에 관한 것이지만 완전히 다른 성격 가지고 있습니다. 되면 scanf함수 연산 종류의 값에 소수점 이하의 수치 표현을 변환하는 데 사용되며, 그 연산 오버플로로부터 보호를 제공하지 않는다. 오버플로가 발생 scanf하면 정의되지 않은 동작이 생성됩니다. 이러한 이유로 C 표준 라이브러리에서 변환을 수행하는 유일한 적절한 방법은 strto...패밀리의 함수 입니다.

따라서 위의 내용을 요약하면 문제 scanf는 문자열 버퍼로 적절하고 안전하게 사용하기가 어렵다는 것입니다 (가능할지라도). 그리고 산술 입력에 안전하게 사용하는 것은 불가능합니다. 후자가 진짜 문제입니다. 전자는 단지 불편합니다.

PS 위의 내용은 전체 scanf기능 제품군에 대한 것입니다 ( fscanf포함 sscanf). 함께 scanf구체적으로는 명백한 문제는 잠재적으로 읽기 위해 엄격하게 형식의 기능을 사용하는 매우 생각한다는 것입니다 대화 형 입력이 오히려 의문이다.


comp.lang.c FAQ에서 : 모든 사람들이 scanf를 사용하지 말라고 말하는 이유는 무엇입니까? 대신 무엇을 사용해야합니까?

scanf여러 문제가 있습니다. 질문 12.17 , 12.18a12.19를 참조하십시오 . 또한 %s형식은 gets()(질문 12.23 참조) 와 동일한 문제를 가지고 있습니다. 수신 버퍼가 오버플로되지 않는다고 보장하기는 어렵습니다. [각주]

보다 일반적으로 scanf비교적 구조화되고 형식이 지정된 입력 용으로 설계되었습니다 (사실 이름은 "스캔 형식"에서 파생 됨). 주의를 기울이면 성공 또는 실패 여부를 알려줄 수 있지만 실패한 지점을 대략적으로 만 알려줄 수 있으며 방법이나 이유는 전혀 알 수 없습니다. 오류 복구를 수행 할 기회가 거의 없습니다.

그러나 대화 형 사용자 입력은 가장 구조화되지 않은 입력입니다. 잘 설계된 사용자 인터페이스는 사용자가 숫자를 예상했을 때 문자 나 구두점뿐 아니라 예상보다 더 많거나 적은 문자를 입력하거나 전혀 입력하지 않을 수 있습니다 ( 예 : RETURN 키) 또는 조기 EOF 또는 기타. 사용할 때 이러한 모든 잠재적 인 문제를 정상적으로 처리하는 것은 거의 불가능합니다 scanf. 전체 줄 ( fgets또는 이와 유사한 것) 을 읽은 다음 sscanf또는 다른 기술을 사용하여 해석하는 것이 훨씬 쉽습니다 . (기능이 좋아하는 strtol, strtok그리고 atoi종종 유용하다 질문을 참조 12.1613.6 .) 당신이 어떤 사용을 할 경우scanf예상되는 항목 수를 찾았는지 확인하려면 반환 값을 확인해야합니다. 또한를 사용하는 경우 %s버퍼 오버플로를 방지해야합니다.

그런데에 대한 비판이 scanf반드시 fscanf및에 대한 기소는 아닙니다 sscanf. scanfstdin일반적으로 대화 형 키보드이며, 대부분의 문제로 이어지는 따라서 최소한의 제한이다. 반면에 데이터 파일의 형식이 알려진 경우 fscanf. sscanf(반환 값이 확인되는 한) 문자열을 구문 분석 하는 것은 제어를 다시 얻고 스캔을 다시 시작하고 일치하지 않으면 입력을 버리는 등의 작업이 매우 쉽기 때문에 완벽하게 적절합니다 .

추가 링크 :

참조 : K & R2 Sec. 7.4 페이지 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.


Yes, you are right. There is a major security flaw in scanf family(scanf,sscanf, fscanf..etc) esp when reading a string, because they don't take the length of the buffer (into which they are reading) into account.

Example:

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

clearly the the buffer buf can hold MAX 3 char. But the sscanf will try to put "abcdef" into it causing buffer overflow.


Problems I have with the *scanf() family:

  • Potential for buffer overflow with %s and %[ conversion specifiers. Yes, you can specify a maximum field width, but unlike with printf(), you can't make it an argument in the scanf() call; it must be hardcoded in the conversion specifier.
  • Potential for arithmetic overflow with %d, %i, etc.
  • Limited ability to detect and reject badly formed input. For example, "12w4" is not a valid integer, but scanf("%d", &value); will successfully convert and assign 12 to value, leaving the "w4" stuck in the input stream to foul up a future read. Ideally the entire input string should be rejected, but scanf() doesn't give you an easy mechanism to do that.

If you know your input is always going to be well-formed with fixed-length strings and numerical values that don't flirt with overflow, then scanf() is a great tool. If you're dealing with interactive input or input that isn't guaranteed to be well-formed, then use something else.


Many answers here discuss the potential overflow issues of using scanf("%s", buf), but the latest POSIX specification more-or-less resolves this issue by providing an m assignment-allocation character that can be used in format specifiers for c, s, and [ formats. This will allow scanf to allocate as much memory as necessary with malloc (so it must be freed later with free).

An example of its use:

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

// use buf

free(buf);

See here. Disadvantages to this approach is that it is a relatively recent addition to the POSIX specification and it is not specified in the C specification at all, so it remains rather unportable for now.


There is one big problem with scanf-like functions - the lack of any type safety. That is, you can code this:

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

Hell, even this is "fine":

scanf("%10s", i);

It's worse than printf-like functions, because scanf expects a pointer, so crashes are more likely.

Sure, there are some format-specifier checkers out there, but, those are not perfect and well, they are not part of the language or the standard library.


The advantage of scanf is once you learn how use the tool, as you should always do in C, it has immensely useful usecases. You can learn how to use scanf and friends by reading and understanding the manual. If you can't get through that manual without serious comprehension issues, this would probably indicate that you don't know C very well.


scanf and friends suffered from unfortunate design choices that rendered it difficult (and occasionally impossible) to use correctly without reading the documentation, as other answers have shown. This occurs throughout C, unfortunately, so if I were to advise against using scanf then I would probably advise against using C.

One of the biggest disadvantages seems to be purely the reputation it's earned amongst the uninitiated; as with many useful features of C we should be well informed before we use it. The key is to realise that as with the rest of C, it seems succinct and idiomatic, but that can be subtly misleading. This is pervasive in C; it's easy for beginners to write code that they think makes sense and might even work for them initially, but doesn't make sense and can fail catastrophically.

For example, the uninitiated commonly expect that the %s delegate would cause a line to be read, and while that might seem intuitive it isn't necessarily true. It's more appropriate to describe the field read as a word. Reading the manual is strongly advised for every function.

What would any response to this question be without mentioning its lack of safety and risk of buffer overflows? As we've already covered, C isn't a safe language, and will allow us to cut corners, possibly to apply an optimisation at the expense of correctness or more likely because we're lazy programmers. Thus, when we know the system will never receive a string larger than a fixed number of bytes, we're given the ability to declare an array that size and forego bounds checking. I don't really see this as a down-fall; it's an option. Again, reading the manual is strongly advised and would reveal this option to us.

Lazy programmers aren't the only ones stung by scanf. It's not uncommon to see people trying to read float or double values using %d, for example. They're usually mistaken in believing that the implementation will perform some kind of conversion behind the scenes, which would make sense because similar conversions happen throughout the rest of the language, but that's not the case here. As I said earlier, scanf and friends (and indeed the rest of C) are deceptive; they seem succinct and idiomatic but they aren't.

Inexperienced programmers aren't forced to consider the success of the operation. Suppose the user enters something entirely non-numeric when we've told scanf to read and convert a sequence of decimal digits using %d. The only way we can intercept such erroneous data is to check the return value, and how often do we bother checking the return value?

Much like fgets, when scanf and friends fail to read what they're told to read, the stream will be left in an unusual state; - In the case of fgets, if there isn't sufficient space to store a complete line, then the remainder of the line left unread might be erroneously treated as though it's a new line when it isn't. - In the case of scanf and friends, a conversion failed as documented above, the erroneous data is left unread on the stream and might be erroneously treated as though it's part of a different field.

It's no easier to use scanf and friends than to use fgets. If we check for success by looking for a '\n' when we're using fgets or by inspecting the return value when we use scanf and friends, and we find that we've read an incomplete line using fgets or failed to read a field using scanf, then we're faced with the same reality: We're likely to discard input (usually up until and including the next newline)! Yuuuuuuck!

Unfortunately, scanf both simultaneously makes it hard (non-intuitive) and easy (fewest keystrokes) to discard input in this way. Faced with this reality of discarding user input, some have tried scanf("%*[^\n]%*c"); , not realising that the %*[^\n] delegate will fail when it encounters nothing but a newline, and hence the newline will still be left on the stream.

A slight adaptation, by separating the two format delegates and we see some success here: scanf("%*[^\n]"); getchar();. Try doing that with so few keystrokes using some other tool ;)

참고URL : https://stackoverflow.com/questions/2430303/disadvantages-of-scanf

반응형