본문 바로가기

BackEnd/C

puts,putchar,gets,getchar 함수와 버퍼

반응형

1.  puts 함수와 putchar 함수


1) puts 함수



이 함수의 기본적인 호출방식은 다음과 같다.


puts("How beautiful day it is!");


이처럼 출력하고자 하는 문자열을(문자열의 주소 값을)인자로 전달하면,


해당 문자열이 출력 된다. 실패시 EOF를 반환한다.


EOF는 End of File로 파일의 끝을 의미하는 -1 상수인데 콘솔출력의 실패를 알리는 용도로도 


사용된다.


2) putchar 함수



출력하고자 하는 문자 정보를 인자로 전달하면, 해당 문자가 출력된다. 


putchar('A');


3) puts 함수와 putchar 함수 예제 코드


1
2
3
4
5
6
7
8
9
#include <stdio.h>
 
int main(void)
{
    puts("ab");
    putchar('c');
    
    return 0;
}
cs


1
2
ab
c
cs


2. gets 함수와 getchar 함수


1) gets 함수



이 함수를 호출하면서 메모리의 주소값을 인자로 전달하면, 키보드로부터 입력되는 문자열


이, 전달된 주소의 메모리에 저장된다.


1
2
3
4
5
6
7
8
9
10
#include <stdio.h>
 
int main(void)
{
    char str[20];
    gets(str); 
    puts(str);
    
    return 0;
}
cs


1
2
abcde
abcde
cs


2) getchar 함수



이 함수는 키보드로부터 입력된 하나의 문자 정보를 반환한다.


따라서 다음과 같은 형태로 호출되어야 한다.


1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>
 
int main(void)
{
    int ch;
    
    ch=getchar();// 변수 ch에 하나의 문자 정보 저장
    
    putchar(ch);
    
    return 0;
}

cs


1
2
a
a
cs

3) getchar 함수의 반환형이 int인 이유는?


char형은 컴파일러에 따라서 unsigned char형으로 처리되기도 하고, signed char형으로 처


리되기도 한다. 그런데 char가 unsigned char로 처리가 되면 문제를 일으킨다. 


왜냐하면 getchar 함수가 반환하는 EOF는 정수 -1이다. 그런데 이 값을 음의 정수를 표현하


지 못하는 unsigned char형 변수에 저장하면 문제가 발생하는 것이다. -1을 unsigned char


형 변수에 저장하면 255가 저장된다. 하지만 int는 컴파일러에 상관없이 항상 signed int로 


표현되기 때문에 안정적인 실행을 보장받을 수 있다. 이러한 이유 때문에 getchar 함수의 반


환형이 int이고, getchar 함수가 반환하는 값도 int형 변수에 저장 하는 것이다.


3. gets 함수의 특성과 입력 버퍼


gets 함수의 문자열 입력 방식



먼저 버퍼라는 것은 임시로 데이터를 저장할 수 있는 메모리 공간을 의미하는 것이다. 그래서 입력


버퍼를 정의하면, 키보드로부터 입력되는 데이터가 임시로 저장되는 메모리 공간이라 할수 있다. 


키보드로 입력하는 데이터들은 엔터 키가 입력되는 순간에 입력 버퍼로 이동이 된다. 그리고 이렇


게 입력버퍼로 이동된 데이터들이 gets 함수나 scanf 함수를 통해서 읽혀지게 되는 것이다. 그렇다


면 이렇게 입력 버퍼를 중간에 두는 이유는 무엇일까?(그냥 다이렉트로 배열에 저장하면 되지, 이렇게 중간 상인을 거치는 이유는 무엇일까)


여기에는 여러 가지 이유가 있다. 하지만 지금은 입력 버퍼의 덕분으로 다양한 형태로 데이터를 해


석할 수 있음(읽어 들임)을 언급하고 싶다. 예를 들어서 키보드를 통해서 기호 7이 입력되었고, 


 기호가 입력 버퍼에 저장되었다고 가정해보자. 이때 이 데이터는 숫자인가? 아니면 문자인가? 


이 기호를 숫자로도(%d),문자로도(%c) 읽어 들일 수 있다. 뿐만 아니라 문자열의 일부로도(%s) 읽


어 들일 수 있다. 이는 입력된 데이터가 입력 버퍼에 저장되기 때문에 가능한 일이다. 


gets 함수는 문자열을 읽어 들이는 함수인데, 데이터의 입력이 실제 이뤄지는 순간은 엔터 키를 입


력하는 순간이므로,엔터키의 입력까지도(\n을 의미함) 문자열의 일부로 입력이 된다. 그런데 


gets 함수는 이렇게 읽어 들인 \n 문자를 프로그래머가 지정한 메모리 공간(일반적으로 배열)에 


저장하기 전에 널 문자로 바꿔버린다. 따라서 gets 함수를 통해서 문자열을 읽어 들이면, 읽어 들


인 데이터의 마지막에는 \n이 아닌 문자열의 끝을 의미하는 \0가 저장된다.


4.  puts 함수의 특성과 출력 버퍼



위 그림이 보여주듯이 puts 함수는 출력을 위해서 메모리(일반적으로 문자열 배열)에 존재하는 문


자열 데이터를 출력 버퍼로 이동시키는 역할을 하며, 더불어 문자열의 끝을 의미하는 널 문자를 개


행을 의미 하는 \n 문자로 변경시키는 역할도 한다. 때문에 puts 함수를 이용해서 문자열을 출력


할 때에는 항상 개행이 이뤄진다. 그리고 이렇게 출력 버퍼에 저장된 데이터들은 들어올 때마다 출


력이 이뤄지기도 하지만, 둘 이상의 데이터가 연속해서 들어오는 경우에는 이들을 묶어서 한번에 


출력시키는 형태로 성능의 향상을 유도하기도 한다(이것이 버퍼가 필요한 이유 중 하나이다).


5. 입력버퍼, 출력버퍼의 필요성


버퍼를 필요로 하는 이유에도 약간씩의 차이는 있지만 기본적으로 버퍼는 성능의 향상을 고려해


서 디자인된다. 앞서 키보드로부터 데이터가 입력되는 모습을 보여줬다. 그런데 이는 생각보다 


복잡한 과정을 거치기 때문에 상대적으로 시스템에 부담이 되는 작업이다. 따라서 최대한 많은 


양의 데이터를 묶어서 한번에 처리를 해야 부담을 줄일 수 있다(승용차보다 버스가 효율적인 이


유에 비유할 수 있다). 바로 이러한 이유로 중간에 입력 버퍼를 둬서 이동시킬 데이터를 모았다가 


한번에 이동시키는 것이다. 그리고 출력 버퍼에도 동일하게 적용되는 이야기이다. 출력시킬 데이


터를 모았다가 한번에 출력을 하면, 그만큼 효율적으로 일을 처리할 수 있다. 


이렇듯 버퍼링을 통한 성능의 향상이 출력 버퍼가 존재하는 큰 이유 중 하나이다.


6.  fflush(stdin) 와 getchar()의 필요성


문자 입출력후 문자열을 입출력하고 싶다고 해보자


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include <stdio.h>
 
int main(void)
{
    int ch;
    char str[20];
 
    /* 문자의 입력과 출력 */
    if((ch=getchar())==EOF)
    {
        printf("문자 READ 실패 \n");
        return -1;
    }
    else
    {
        if(putchar(ch)==EOF)
            printf("문자 WRITE 실패 \n");
        putchar('\n');
    }
 
    /* 문자열의 입력과 출력 */
    if(gets(str)==NULL)
    {
        printf("문자열 READ 실패 \n");
        return -1;
    }
    else
    {
        if(puts(str)==EOF)
            printf("문자열 WRITE 실패 \n");
    }
 
    return 0;
}
cs

1
2
3
4
5
6
7
8
a
a
 
 
--------------------------------
Process exited after 1.088 seconds with return value 0
계속하려면 아무 키나 누르십시오 . . .
 
cs

문자를 입출력 하자마자 프로그램이 종료된 상황을 보여준다. 즉 문자열의 입출력 기회가 사라진 것이


다. 이 현상은 한 문자를 읽어 들이는 getchar 함수에 원인이 있다. 문자열을 읽어 들이는 gets 함수는 


문자열의 끝에 입력되는 \n까지 읽어 들여서 이를 \0으로 교체해 버리지만, 문자를 읽어 들이는 


getchar 함수는 하나의 문자만을 읽어 들이기 때문에 문자의 입력을 위해 더불어 입력되는 \n이 입력 


버퍼에 남는 상황이 발생한다. 그리고 이는 이후에 호출되는 gets 함수에 의해서 읽혀지게 되어(gets 함


수는 하나의 문자열을 읽어 들이는 함수이므로 \n을 만날 때까지만 데이터를 읽어 들인다),결과적으로 


문자열을 입력할 기회를 놓치게 되는 것이다. 


위의 실행결과는 문자열의 출력이 이뤄지지 않은 것처럼 보이지만 실제로는 \n이 하나의 문자열로 출


력된 것이다. 우리는 새로운 문자열을 입력 받기 원한다면, 입력 버퍼에 불필요하게 남아있는 \n을 지


워버리면 된다. 다시 말해서 입력 버퍼를 깨끗이 비워버리면 된다. 그리고 이를 위해서 fflush라는 이름


의 함수를 호출하면 된다.



위 함수를 보면서 당분간은 다음과 같은 형태로 호출을 한다고만 기억을 하자.


fflush(stdin); 


인자로 전달되는 stdin은 입력 버퍼를 의미한다.


즉 위의 함수 호출은 입력 버퍼를 비워라라는 의미다.


그러나 fflush함수는 VC++ 계열의 컴파일러에서만 정상적으로 동작한다.


그래서 핵심적으로 이문제를 해결하려면 입력 버퍼를 비워야 한다.


그 해결법은 getchar();


위 함수의 호출이 이뤄진다면, 메모리 공간에 남아 있는 개행 문자가 읽혀져서 입력 버퍼에 남았던 개


행 문자는 삭제가 된다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include <stdio.h>
void ClearReadBuffer(void);
 
int main(void)
{
    int ch;
    char str[20];
 
    /* 문자의 입력과 출력 */
    if((ch=getchar())==EOF)
    {
        printf("문자 READ 실패 \n");
        return -1;
    }
    else
    {
        if(putchar(ch)==EOF)
            printf("문자 WRITE 실패 \n");
        putchar('\n');
    }
 
    /* fflush(stdin) 대행 */
    ClearReadBuffer();
 
    /* 문자열의 입력과 출력 */
    if(gets(str)==NULL)
    {
        printf("문자열 READ 실패 \n");
        return -1;
    }
    else
    {
        if(puts(str)==EOF)
            printf("문자열 WRITE 실패 \n");
    }
 
    return 0;
}
 
void ClearReadBuffer(void)
{
    while(getchar()!='\n');
}
cs


1
2
3
4
a
a
bb
bb
cs


위 예제의 23행에서 호출하고 있는 ClearReadBuffer 함수는 내부적으로 Wn을 읽어 들일 때까지 


getchar 함수가 호출되도록 정의되어 있다. 따라서 입력 버퍼에 남아 있는 Wn 뿐만 아니라,Wn 앞에 


남아 있는 데이터들도 더불어 비워진다.


7.  printf, scant 함수와의 차이점은? 


1) 성능적인 측면


문자와 문자열의 입출력 기능은 printf 함수와 scanf 함수의 호출을 통해서도 얼마든지 구현


이 가능하다. 그렇다면 이들의 차이점은 무엇일까? 우선 printf 함수와 scanf 함수는 위에서 


설명한 함수들에 비해서 훨씬 기능이 막강하다(입력과 출력의 형태가 다양하므로). 


기능이 다양한 만큼 성능적인 측면에서는 조금 뒤쳐질 수밖에 없다. 함수 내에서 고려해야 


할 경우의 수가 훨씬 더 많기 때문 이다. 따라서 위에서 소개한 함수들로도 충분한 상황에서


는 굳이 scanf 함수와 printf 함수를 사용하지 않는 것이 성능에는 도움이 된다. 


     

2) 기능적인 측면


1
2
3
4
5
6
7
8
9
10
11
12
13
#include <stdio.h>
 
int main(void)
{
    char name[20];
    printf("이름: ");
    gets(name);
 
    printf("%s \n", name);
    puts(name);
 
    return 0;
}
cs


위 예제의 7행에서는 gets 함수를 이용해서 문자열을 입력 받고 있다. 그런데 gets 함수는 


scanf 함수와 달리 개행 문자를 통해서 데이터를 구분한다. 따라서 다음과 같은 형태의 문자


열은 모두 입력이 가능하다.


“홍길동“

“홍 길동“

“홍 길 동”


 scanf 함수는 공백을 기준으로 데이터를 구분하기 때문에 첫 번째 형태의 문자열만 입력이 


가능 하다. 그리고 위 예제 10행에서는 문자열을 출력하고 있는데, puts 함수는 문자열 출력 


시 기본적으로 개행이 이뤄진다. 반면 printf 함수를 이용해서 10행과 동일한 결과를 기대하


려면 9행과 같이 문장을 구성 해야 한다. 9행보다 10행의 문장구성이 훨씬 간단하다.


반응형