|

C 언어 scanf fgets 차이: 입력 버퍼와 문자열 실수 줄이기

C scanf fgets 입력 버퍼 문자열 처리 대표 이미지
C 언어 문자열 입력은 함수 이름보다 버퍼 크기와 개행 처리 기준이 더 중요합니다.

scanf fgets 차이는 단순히 어떤 함수가 더 좋은가의 문제가 아닙니다. C 언어에서 `scanf`는 형식에 맞는 토큰을 읽는 데 강하고, `fgets`는 문자열 한 줄을 비교적 안전하게 읽는 데 적합합니다.

핵심은 문자열 입력에서는 버퍼 크기와 공백, 개행 처리를 직접 통제해야 한다는 점입니다. 이 글은 C 표준 라이브러리 문서와 POSIX 설명을 기준으로 `scanf`와 `fgets`의 차이를 예제로 정리합니다.

C scanf와 fgets 차이 요약 카드
scanf는 토큰 입력에 가깝고, fgets는 한 줄 입력에 가깝습니다.

C scanf fgets 차이의 출발점

C에서 입력은 자동으로 안전해지지 않습니다. 배열 크기를 개발자가 정하고, 문자열 끝의 null character도 고려해야 합니다. 그래서 입력 함수 선택은 생각보다 중요합니다.

`scanf`는 형식에 맞춰 값을 읽는 함수입니다. 정수 하나, 실수 하나처럼 명확한 토큰 입력에는 유용합니다. 하지만 사용자가 입력한 한 줄 문자열을 그대로 받고 싶을 때는 예상과 다르게 동작하기 쉽습니다.

scanf(“%s”)는 공백에서 멈춘다

`scanf(“%s”, name)`은 문자열을 읽지만, 공백 문자를 만나면 입력을 멈춥니다. 그래서 이름에 공백이 들어가거나 문장을 읽고 싶은 경우에는 적합하지 않습니다.

#include <stdio.h>

int main(void) {
    char name[20];
    scanf("%19s", name);
    printf("[%s]\n", name);
    return 0;
}

입력으로 `hello world`를 넣으면 `hello`까지만 name에 들어갑니다. `world`는 입력 버퍼에 남아 다음 입력에 영향을 줄 수 있습니다.

폭 지정 없는 scanf는 위험하다

`scanf(“%s”, name)`처럼 폭을 지정하지 않으면 배열 크기보다 긴 입력이 들어올 수 있습니다. C 문자열은 자동으로 크기를 늘려주지 않기 때문에 버퍼를 넘는 쓰기가 발생할 수 있습니다.

char name[20];

// 위험: 입력 길이를 제한하지 않는다
scanf("%s", name);

// 그나마 나은 형태: 최대 19글자 + '\0'
scanf("%19s", name);

폭을 지정하면 위험이 줄지만, 여전히 공백 포함 문자열을 읽는 문제와 입력 버퍼에 남는 문자 문제는 따로 생각해야 합니다.

fgets는 버퍼 크기를 함께 받는다

`fgets`는 읽을 배열과 크기, 입력 스트림을 함께 받습니다. 지정한 크기 안에서 한 줄을 읽고 문자열 끝에 null character를 붙입니다.

#include <stdio.h>

int main(void) {
    char line[100];

    if (fgets(line, sizeof(line), stdin) != NULL) {
        printf("[%s]\n", line);
    }

    return 0;
}

이 방식은 공백을 포함한 문자열을 받을 수 있고, 버퍼 크기를 코드에서 확인하기 쉽습니다. 그래서 사용자 입력 한 줄을 받을 때는 `fgets`가 더 예측 가능한 선택이 되는 경우가 많습니다.

fgets의 개행 문자 처리

`fgets`는 줄 끝의 newline을 읽으면 그 문자를 버퍼에 포함합니다. 그래서 입력값 비교나 문자열 저장 전에 개행 문자를 제거해야 할 때가 많습니다.

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

int main(void) {
    char line[100];

    if (fgets(line, sizeof(line), stdin) != NULL) {
        line[strcspn(line, "\n")] = '\0';
        printf("[%s]\n", line);
    }

    return 0;
}

`strcspn(line, “\n”)`은 문자열에서 개행 문자가 처음 나타나는 위치를 찾습니다. 그 위치에 `\0`을 넣으면 줄 끝 개행을 제거할 수 있습니다.

숫자는 fgets로 읽고 sscanf로 파싱할 수도 있다

입력 전체를 먼저 한 줄로 받고, 그 다음 `sscanf`로 필요한 값을 해석하면 입력과 파싱 단계를 분리할 수 있습니다. 오류 처리도 더 명확해집니다.

char line[100];
int age;

if (fgets(line, sizeof(line), stdin) != NULL) {
    if (sscanf(line, "%d", &age) == 1) {
        printf("age = %d\n", age);
    } else {
        printf("숫자를 입력해야 합니다.\n");
    }
}

이 방식은 입문 단계에서는 조금 길어 보이지만, 사용자가 잘못 입력했을 때 프로그램을 더 차분하게 처리할 수 있습니다.

언제 scanf를 써도 괜찮을까

  • 문제 풀이에서 입력 형식이 엄격하게 정해져 있을 때
  • 정수나 실수처럼 공백으로 구분되는 토큰을 빠르게 읽을 때
  • 폭 지정과 반환값 확인을 함께 할 수 있을 때
  • 사용자 자유 입력이 아니라 테스트 데이터 입력일 때

실무 체크리스트

  1. 문자열 한 줄을 받아야 하면 `fgets`를 먼저 검토한다
  2. 배열 크기를 `sizeof(buffer)`로 함께 넘긴다
  3. 필요하면 newline을 제거한다
  4. 숫자 변환은 `sscanf`나 `strtol`처럼 별도 단계로 나눈다
  5. `scanf`를 쓴다면 폭 지정과 반환값 확인을 빼먹지 않는다

정리

C에서 `scanf`와 `fgets` 중 하나가 항상 정답은 아닙니다. 다만 사용자 문자열 입력에서는 공백, 버퍼 크기, 개행 처리를 명확히 다룰 수 있는 `fgets`가 더 안전한 출발점이 되는 경우가 많습니다.

C 입력 감각은 알고리즘 입력 처리와도 연결됩니다. 그래프 문제는 입력부터 다르다, KMP 알고리즘의 pi 배열은 왜 필요한가, 0-1 BFS는 다익스트라와 무엇이 다를까를 함께 보면 입력 구조를 더 넓게 볼 수 있습니다. 외부 기준은 cppreference – scanf, cppreference – fgets, POSIX – fgets를 확인했습니다.

함께보면 좋은 글