brunch

매거진 Sinclair

You can make anything
by writing

C.S.Lewis

by Sinclair Feb 10. 2016

제어문과 반복문

그리고 프로그램은 계속된다 IV




scanf()함수의 문제점 해결: 정수형 입력함수 작성하기


이미 우리가 앞에 작성한 이차방정식의 근을 구하는 프로그램을 반복 실행하도록 바꿔 보았습니다. 하지만 제대로 동작 하지 않습니다. 왜일까요? 게다가 문자열이나 실수형을 입력하면 프로그램이 미친 듯이 난동을 부릴 것 입니다. 프로그램이 미쳤나 봐요~   


#include <stdio.h>

#include <stdlib.h>

#include <math.h>  

/*

* copyleft (l) 2006 - 2017 programmed by Sinclair

*/   

int main() {  

    int aValue , bValue , cValue ;  

    while(1004) {

        puts("정수형 세 개를 입력하시면 이차방정식의 근을 구합니다.") ;  

        inputData( &aValue , &bValue , &cValue ) ;

        if( !aValue ) { // a값이 0 이면 근의 공식 사용불가능

            puts("a 값이 0이면 근을 구할 수 없어서 프로그램을 종료합니다.") ;

            return 1 ;

        } // end if

        getResult( aValue , bValue , cValue ) ;

        printf("%s" , "한 번 더 하시겠습니까? (y/n) " ) ;

        if( tolower(getchar()) == 'n' ) break ;

        // tolower()함수는 대문자를 소문자로 바꿔주는 함수입니다.

        // <ctype.h>에 프로토타입이 선언되어있습니다.

    } // end while  

    return 0 ;

} // end main()    



일단 buffering문제는 위대한 한 줄의 소스 while(getchar() ^ '\n') ;으로 해결할 수 있지만 정수형을 확인하는 문제는 어떻게 해야 할까요? 일반적으로 두 가지 방법이 있습니다. 첫 번째 키보드에서 숫자만 입력되게 하는 방법이 있고, 두 번째 입력이 된 후에 숫자가 아닌 것이 있다면 잘못 입력했으니 다시 입력을 받는 방법이 있습니다. 일단 첫 번째 방법은 표준 C에서 제공하는 함수로는 할 수 없습니다. 표준 함수의 I/O는 거의 모두 buffering을 하는데 키보드 입력을 바로 확인하려면 buffering을 하지 않는 비 표준 함수를 사용해야 합니다. 때문에 두 번째 방법을 사용하겠습니다. 여기서 우리가 알아야 할 것은 사실 거의 모든 입출력은 문자열이라는 것입니다. 우리가 키보드의 숫자를 입력해도 그것은 코드 값들이고 그것을 내부에서 숫자로 변환하는 것입니다. 그렇기 때문에 문자열로 입력을 받아서 그 안에 코드 값을 비교하여 문자코드가 있으면 다시 입력 받는 방법을 사용하겠습니다.


순서는 다음과 같습니다.

1) 문자열 입력 받습니다.

2) 숫자만 들어있는지 체크합니다.

    a) 사이즈를 잽니다. 사이즈가 0 이면 엔터만 입력한 것 입니다.

    b) 처음에 부호가 있는지 체크합니다.
        부호가 있는데 사이즈가 1이면 잘못된 입력한 것 입니다.

        c) 하나하나 검사 후 중간에 문자코드가 있으면 잘못된 입력이니 중지합니다.  

3) 검사가 끝나면 atoi() 함수를 사용하여 정수형으로 변환시킵니다.  



scanf("%s")는 buffering문제를 가지고 있으니 사용하지 않겠습니다. 공백과 enter는 입력이 안될 뿐 아니라 사이즈 문제로 메모리가 터질 수 도 있습니다. 이것이 바로 유명한(?) 세그멘테이션 폴트 입니다.


gets()함수는 문자열을 한꺼번에 입력 받는 멋진 함수입니다. buffering문제도 없습니다. 그러나 더 무서운 문제가 발생합니다. 문자열의 사이즈를 체크하지 않고 무조건 받아들입니다. 그게 무슨 문제냐고요? 메모리를 10bytes 잡았습니다. 키보드 입력은 100문자를 했습니다. 100문자가 10bytes에 들어가면 어찌 되겠습니까? 역시 메모리가 터집니다. 메모리가 터지면 우리가 늘(?) 만나게 되는 segmentation fault 에러가 발생하는 것입니다. Segment는 쉽게 말하자면 메모리에 그어놓은 금입니다. 그러니 segmentation fault는 메모리 금 밟았다는 얘기가 아니겠습니까? 그 동안 그냥 프로그래밍 했지만 이제는 그렇게 살면 안됩니다. "고객님하~ 제발 10글자만 입력해주세요." 너무나 슬프고 가슴 아픈 메시지 아닙니까? 일반 사용자를 절대 믿지 마세요. 그래서 저희는 fgets()라는 길이를 확인해주는 함수를 사용하겠습니다. 그런데 이 녀석은 buffering 문제와 줄 바꿈 문자인 '\n'이 함께 입력되는 문제를 가지고 있습니다. 표준 함수들이 모든 문제를 가장 완벽하게 가장 빠르게 처리하고 있다고 생각하면 안됩니다. 표준 함수들은 최소한의 기능만을 표함하고 있습니다. 우리가 그 부족한 기능을 보완해서 사용해야 합니다. 세상에 믿을 함수 하나 없더라. 프로그래밍 10년 만에 터득되었습니다.   



fgets() 함수 보완 i: getchar()사용  


/*

* copyleft (l) 2006 - 2017 programmed by Sinclair

*/   

//char *fgets(char *s, int n, FILE *stream);

int fgets1(register char * s , int n , FILE * stream ) {

    register char c ;

    register int i ; // for index ;  

    if(n <= 0 ) return -1 ; //문자열이 있어야 입력을 받지?

   

    i = 0 ; n-- ;

    while( (c = getchar()) ^ '\n') {

        if(i < n ) s[i++] = c ;

        // 버퍼링 문제는 해결..  

        // 사이즈보다 큰 넘은 어쩔 것이냐?

        else {

            i++ ;//return 1 ;
             //지정사이즈 보다 큰 넘은 오류일까? 결정하길

        } // end else

    } // end while

    if (i > n ) {

        s[n] = 0 ; // 왜? 그래야 문자열이다..  나중에 배웁니다.

        return 1 ;

    } // end if

    else {

        s[i] = 0 ;

        return 0 ;

    } // end else  

} // end fgets1() ;    



fgets() 함수 보완 ii: fgets()의 문제점을 해결  


/*

* copyleft (l) 2006 - 2017 programmed by Sinclair

*/   

int fgets2(register char * s , int n , FILE * stream ) {

    if( ! fgets( s , n , stream ) ) return -1; // 입력실패..  

#ifdef __FIRST___

    if( !(s[strlen(s)-1] ^ '\n')) // 엔터문자 처리..

        s[strlen(s)-1] = 0 ; // <- 이넘까지만 문자열이다.. 

    else

        while(getchar()^'\n') ;

#endif

    if( s[n = strlen(s)-1] ^ '\n')

        while(getchar()^'\n') ; // buffering 문제 해결

    else 

        s[n] = 0 ; // <- 이넘까지만 문자열이다..

    return 0 ;  

} // end fgets2() ;    



fgets() 함수 보완 iii: 항상 stdin만을 사용한다는 것을 고려  


/*

* copyleft (l) 2006 - 2017 programmed by Sinclair

*/   

int fgets3(register char * s , int n ) {

    if( ! fgets( s , n , stdin ) ) return -1; // 입력실패..  

    if( s[n = strlen(s)-1] ^ '\n')

        while(getchar()^'\n') ;

    else

        s[n] = 0 ; // <- 이넘까지만 문자열이다..  

    return 0 ;  

} // end fgets3() ;    




fgets() 함수 보완 iv: 정해진 사이즈를 넘겼다는 사실을 알리기  


/*

* copyleft (l) 2006 - 2017 programmed by Sinclair

*/   

int fgets4(register char * s , int n ) {

    if( ! fgets( s , n , stdin ) ) return -1; // 입력실패..  

    if( s[n = strlen(s)-1] ^ '\n' ) {

        n = 0 ;

        while(getchar()^'\n') n -- ;

        if( n ) return 1 ; // 지정사이즐 넘겼다네~

    } // end if

    else // 엔터문자 처리..

        s[n] = 0 ; // <- 이 넘까지만 문자열이다..  

    return 0 ;  

} // end fgets4() ;  




실제 Sinclair가 사용하는 키보드 문자열 입력용 fgets4()


#include <stdio.h>  

/*

* copyleft (l) 2006 - 2017 programmed by Sinclair

*/   

int strlen2(register const char *) ;  

int fgets4(register char * s , int n ) {

    if( ! fgets( s , n , stdin ) ) return -1; // 입력실패..  

    if( *(s + (n = strlen2(s)-1)) ^ '\n' ) {

        n = 0 ;

        while(getchar()^'\n') n -- ; // 버퍼를 비우면서 n을 감소

        if( n ) return 1 ; // 지정사이즐 넘겼다네~

    } // end if     

    else *(s + n) = 0 ; // <- 이 넘까지만 문자열이다  

    return 0 ;  

} // end fgets4() ;   


int strlen2(register const char * s) {  

    register int count = 0 ;  

    while( *s++) count ++ ;  

    return count ;  

} // end strlen2()    




자, 이제 이것을 활용하여 이차방정식의 근을 구하는 프로그램을 바꿔 보도록 하겠습니다.


















#Sinclair #씽클레어 #싱클레어 #씽클레어도씨 #씨언어 #씨프로그래밍  #C언어 #Cprogramming #C_Programming #C #Programming #Clanguage #C_Language

매거진의 이전글 제어문과 반복문
작품 선택
키워드 선택 0 / 3 0
댓글여부
afliean
브런치는 최신 브라우저에 최적화 되어있습니다. IE chrome safari