살아가는 이야기

C의 fgets 사용법(gets 이식 방법) 본문

컴퓨터, 풀어그림

C의 fgets 사용법(gets 이식 방법)

우균 2021. 9. 23. 08:25

C의 gets는 매우 사용하기 편리하지만, 초기 설계의 문제로 인해 버퍼 오버플로(buffer overflow: 지정한 영역을 넘어 기록하는 현상)가 발생한다는 치명적 오류가 있다. 그래서 지속적으로 지원 중단 예정(deprecated) 함수로 지정되었다가 C11 이후로는 아예 지원되지 않는다.

문제는 이전에 gets를 이용하여 사용하던 코드의 경우 다시 작성해야 하는데, 이때 fgets를 사용해야 한다. fgets의 프로토타입은 다음과 같다.

char *fgets(char *버퍼, size_t 크기, FILE *입력스트림);

따라서 gets로 다음과 같이 작성된 코드가 있다고 하면

gets(line);

다음과 같이 다시 작성해야 한다.

fgets(line, sizeof line, stdin);

다만 줄바꿈 문자(newline character)를 다루는 방식이 조금 다른데, gets는 줄바꿈 문자까지 읽고 저장하지 않지만, fgets는 줄바꿈 문자까지 읽고 이를 저장한다. 그러므로 gets에서 사용했던 버퍼 크기를 fgets에서는 하나 크게 하는 것이 안전하다.

// char line[80];과 같이 선언되었었다면
char line[81];

그리고 gets와 똑같이 동작하도록 하려면 fgets 수행 후 줄바꿈 문자를 점검하여 처리해 주어야 한다.

if (line[strlen(line)-1] == '\n')
    line[strlen(line)-1] = '\0';

gets를 fgets로 바꾸어 쓴 결과, 포팅된 코드 전체는 다음과 같다.

#include <stdio.h>
#include <string.h>	// strlen 호출 때문에 필요함

int main() {
    char line[81];	// 버퍼 크기는 한 바이트 크게 바꿈

    // gets(line);을 바꾸어 쓴 코드
    fgets(line, sizeof line, stdin);
    if (line[strlen(line)-1] == '\n')
        line[strlen(line)-1] = '\0';
    
    puts(line);
    return 0;
}

strlen을 두 번 호출하는 것이 마음에 걸린다면 별도의 변수를 만들어 미리 저장해 두면 된다.

조금 지저분해지긴 했지만, 이제 안전하게 지낼 수 있어서 좋다. 무엇보다도 gets는 이제 사용할 수 없으니 바꾸어 쓰는 수밖에 없다. 감염병을 퇴치하려면 인내가 필요한 것처럼 말이다.

 

용이 되기 위한 사족

fgets 사용 시, 매개변수 순서를 잊는 경우가 많다. 이때에는 C 언어의 설계 원칙을 생각하면 기억하기 쉽다. fgets의 경우도 그렇지만, C 라이브러리에서 어디에 저장하는 것이 필요한 경우에는 첫 번째 매개변수로 주어지는 경우가 대부분이다. 이는 strcpy의 경우에도 그러한데, 하필 이 순서가 우리말 순서와 달라서 기억하기 어려울 수 있다. s를 t로 복사하라는 뜻에서 무심코 strcpy(s, t)와 같이 쓸 수 있기 때문이다. 그런데 strcpy(t, s)로 써야 한다.

이를 잊지 않으려면 ― C의 설계 원칙에 따라 ― 대입문의 방향을 생각하면 좋다. 변수 b에 a를 대입하려면 b = a처럼 사용해야 하는 것처럼, strcpy의 경우에도 t에 s를 복사하려면 strcpy(t, s)로 쓰고, buf에 stdin에서 읽은 값을 기록하려면 fgets(buf, sizeof buf, stdin)과 같이 써야 한다.

b = a;                           // b <- a

strcpy(t, s);                    // t <- s

fgets(buf, sizeof buf, stdin);   // buf <- stdin

fscanf나 sscanf의 경우엔 반대가 아니냐고 반문할 사람들이 있을지도 모르겠다. 이들 함수의 경우에는 우측으로 몇 개의 변수가 주어질지 알 수 없기 때문에, 어쩔 수 없이 첫 번째 인수를 스트림으로 삼을 수밖에 없었을 것이다. 아량이 넓은 당신이 이해해 주기 바란다.

Comments