C 언어(22) - 버퍼와 입력 심화 이해

C 언어 강의, 버퍼를 통한 입력 심화 이해

Featured image

🔚 짧게 하는 복습

✅ 1. 지역 변수, 전역 변수, 정적 변수의 정의와 생존 기간, 유효 범위 차이를 안다.

혹시 기억이 안 난다면, 다시 돌아가자


아주 오래전 과제에서 이런 것을 다룬 적이 있었다.

_기호(+, -, _, /), 숫자1, 숫자2를 입력받아 계산 값을 출력하는 계산기를 만들어보자(난이도 下) (입력받는 순서는 무조건 기호, 숫자1, 숫자2로 할 것!)*

그런데 혹시 이 과제를 다른 순서로 해본 사람이 있을까? 어떤 순서든 기호가 숫자 뒤에 입력받으면, 결과가 제대로 나오지 않을 것이다.(궁금하다면 아래 코드를 실행시켜보자)

#include<stdio.h>

int main() {
    int num1, num2;
    char op;

    scanf("%d %d", &num1, &num2);
    scanf("%c", &op);

    switch(op){
        case '+' : printf("%d", num1+num2); break;
        case '-' : printf("%d", num1-num2); break;
        case '*' : printf("%d", num1*num2); break;
        case '/' : printf("%d", num1/num2); break;
        default : printf("error");
    }
}

왜 이런 일이 일어나는 걸까? 해답은 버퍼scanf에 있다.


버퍼란?

버퍼문자열 입력을 편리하게 하기 위해 만들어진 임시 공간 같은 것이다.

우리가 키보드를 누르는 순간마다 자동으로 stdin 버퍼(표준 입출력 버퍼)라는 공간에 우리가 작성한 모든 문자가 담긴다.(앞으로 그냥 버퍼라고 함)

Enter를 누르기 전까지는 임시 저장된 값을 자유롭게 수정 혹은 지우기 가능하다.

그러다가 enter를 누르는 순간, 그 시점에 stdin 버퍼에 저장된 값의 마지막에 ‘\n’이라는 문자가 붙으며 버퍼에 최종적으로 저장된다.

예를 들어, “Hello I am Minsu”라고 적고 enter를 치면, 버퍼에는 “Hello I am Minsu\n”으로 저장이 된다는 뜻이다.

입력이 끝난 후에는, 오직 이 버퍼에 저장된 값으로만 프로그램을 수행하게 된다.


scanf의 비밀

우리는 scanf에 대해서 format에 맞추어 입력받고, 그 값을 변수 시작 주소를 이용해 저장하는 함수로 배웠다.

그런데 과연 그럴까? 아래 코드를 정수 빼고 모두 입력해보자.

이게 무슨 일일까? 입력된다.

사실 scanf의 format이 정수실수인 경우, 굳이 형식에 맞추어 입력받지 않는다.

형식이 필요한 것은 입력의 형태가 아니라, 변수에 저장할 때이기 때문이다.

또한, 이들에게 예외가 한 가지 있는데, 첫 입력이 \n(줄 바꿈), \t(탭), ‘ ‘(스페이스) 같은 공백문자가 오면 공백문자가 아닌 값이 나올 때까지 무시한다.

그렇다면 %c는 어떨까? 아래 코드에 \n, 즉 엔터를 입력해보자.

놀랍게도 \n가 첫 입력이더라도 바로 입력되는 것이 보인다.

마지막으로, 우리가 아직 다루지 않은 형식 지정자 중 %s라는 것이 있다.

이는 문자들을 모아놓은 문자열을 나타내는 것으로, 공백문자가 나올 때까지 입력을 받는다. (문자열에 대해서는 추후 강의에서 조금 더 자세히 다루겠다.)

입력의 마지막에 0, ‘\0’, (char)NULL이라는 널 문자를 붙여서 문자열의 끝을 알린다. (3가지의 널 문자는 모두 같은 것으로 취급된다.)

또한, printf 역시 %s라는 형식 지정자를 통해 출력할 수 있고 널 문자를 만날 때까지 출력한다.

아래 예제 코드를 보며 이해하자.


버퍼를 통한 scanf 이해

자 이제 위에서 배운 두 개념을 동시에 적용해서 이해해보겠다.

    scanf("%d %d", &num1, &num2);
    scanf("%c", &op);

자 여기가 문제의 부분인데, 우리가 만약 “1 2”를 입력한다고 하자.

그러면 엔터를 치는 그 순간, 뒤에 줄 바꿈이 붙어서 “1 2\n”으로 입력이 된다.

  1. 첫 번째 %d는 1이 가장 앞에 있으니 입력되고 버퍼에서 지워진다.

  2. 두 번째 %d는 그다음 가장 앞에 있는 ‘ ‘라서 무시가 되고(버퍼에서 지워지고), 다음 앞에 있는 2가 입력된다.

  3. 세 번째 %c는 버퍼가 비었다면 우리에게 입력의 기회를 줬겠지만, 안타깝게도 버퍼에는 \n가 있다. 즉, 바로 \n이 입력된다.

그렇다면 이 문제를 어떻게 해결할 수 있을까?


입력 버퍼를 다루는 함수(gets, getchar)

입력 버퍼를 이용해 입력하는 함수는 scanf만 있는 것이 아니다.

여기서는 총 2가지 함수에 대해 다룰 것인데, gets, getchar이다.


다양한 입력 함수 - gets

우선 가장 간단한 gets에 대해 알아보겠다.

gets는 get string의 줄임말로, 엔터를 누르는 그 순간 버퍼에 있는 모든 값을 문자열에 저장하고 버퍼를 비우는 함수이다.

scanf(“%s”)처럼 마지막에 널 문자를 붙여준다는 점은 비슷하나, gets는 공백문자가 얼마나 있던 상관없이 모두 그대로 저장한다는 점이 다르다.

아래 코드에 “Hello, I am Minsu”를 입력해보자


다양한 입력 함수 - getchar

getchar는 말 그대로 get char로, 입력 버퍼의 가장 앞에 있는 문자 하나만 가져오고 버퍼를 지우는 함수이다.

만약 읽은 문자가 있다면 그 문자를 아스키코드로 반환하고, 읽을 문자가 없다면 EOF(End of File)을 반환한다.

예를 들어, 방금 예제 코드에서 중간에 Hello만 입력되고 남은 버퍼에서 이런 조작이 가능하다.

아래의 코드에 Hello, I am Minsu를 이번엔 2번 입력해보자.

아래 순서와 같이 처리된다.

  1. 첫 번째 Hello, I am Minsu를 입력한다.

  2. Hello, 까지만 str에 입력되고 나머지는 버퍼에 그대로 저장되어있다. str을 출력한다.

  3. while과 getchar를 통해서 앞에서부터 한 글자씩 가져오되, \n을 만나면, 즉 문장이 끝나면 반복을 멈춘다.

  4. gets는 공백을 포함한 모든 문자를 입력받으니, Hello, I am Minsu가 모두 입력된다.

  5. 출력되고 종료된다.

📖 오늘의 핵심(다 알기 전까지는 넘어가지 말자❗)

✅ 1. 버퍼에 대해 안다.

✅ 2. scanf()의 특징을 버퍼와 연결 지어 이해한다.

✅ 3. getchar(), gets()를 자유자재로 사용할 수 있다.

⚠️ scanf(“%c”)는 사용을 자제하자.

💣 과제, 없음.

🔜 더 공부해보기,

  1. 읽어볼 거리(1) - 버퍼 오버플로우란?

  2. 읽어볼 거리(2) - 버퍼 오버플로우를 이용한 해킹