프로그래밍/C

4.3 외부변수(External Variables)

피노리코 2014. 11. 1. 17:32

외부(external) 라는 말은 함수 내에서 정의되는 매개변수를 나타내는 내부(internal)라는 말과 대조되어 사용된다. 외부면수들은 바깥에서 정의되므로 여러 다른 함수들이 사용 할 수있다. 함수는 항상 외부형으로 정의되고 함수내에서 다른함수들이 정의될수 없다 외부면수는 또한 전체적으로 사용되기 때문에 함게 사용될 모든 파일 내에 같은 이름의 외부변수는 하나만 있어야 한다.(이런 특성을 external linkage 라고 부른다) 전체적으로는 이용할수 없지만 하나의 소스 파일 내에서만 사용되는 외부변수나 함수를 어떻게 정의하는가에 대해서는 나중에 설명하겠다 외부형 변수들은 전체적으로 사용할수 있기 때문에 데이터를 교환하는 데 사용되는 매개변수나 리턴 값과 같은 용도로 사용될수 있다. 만일 어떤 외부변수의 이름이 선언되었다면 어떤 함수이든 그 외부변수를 사용할 수있다.

많은 변수가 함수들 사이에 공유되어야 한다면 외부변수를 사용하는 것이 매개변수를 사용한느것보다 훨씬 편리하다 그러나 제1장에서도 지적했듯이 조심스럽게 사용해야 한다. 외부변수를 많이 사용하면 프로그램의 구조가 나빠질수도 있고 함수들 간에 많은 데이터의 연결이 필요해지기 때문이다

자동변수(automatic variable)는 함수에 대해서 내부형이다.자도연수는 그 루틴이 수행될때 생겼다가 수행이 끝나면 사라진다. 한편 외부변수는 영구적이다. 외부변수는 한번 생성되면 없이지지 않으므로 함수가 한번 호출된후 다음 호출이 있을때 까지 값을 보존한다 그러므로 두 함수가 어떤 데이터를 공우해야 하지만 서로 상대방을 호출하지 않는다면 매개변수를 통한 값의 전달보다 외부변수를 사용하는것이더 편리하다.이를 예제를 통해 살펴보자 이 프로그램은 +,-,*,/ 동작을 하는 계산기 프로그램이다. 이는 구현하기 쉬운편 이므로 infix 대신에 reverse polish 표기를 사용하기로 하자 reverse polish 표현은 forth나 postscript 같은 언어 또는 일부 휴대용 계산기에서 사용하고 있다

reverse polish 표현은 각 연산자들이 그들의 오퍼랜드 뒤에 따라오는 형식이다.


( 1 - 2 ) * ( 4 + 5 )


와 같은 infix 표현은


1 2 - 4 5 + *


와 같은 reverse polish 표현과 같은 의미이며 괄호는 필요하지 않다.

이것은 구현이 간단하다. 각 오퍼랜드는 스택을 저장하고 연산자가 나타나면 적당한 수의 오퍼랜드를 취하여 연산하며 그 결과를 다시 스택에 넣어진다 위의 예제에서 1과 2가 스택에 넣어지고 그들의 차1 이 그들의 자리를 차지하게 된다. 그다음에 4와5가 스택에 넣어지고 그들의 함인 9가 글의 자리에 넣어지고 1과 9의 곱인 9가 스택에 남는다 따라서 입력 라인의 끝을 만나면 스택에서 9를 꺼내 출력기키면 된다.

이프로그램은 각 오퍼랜드나 연산자가 나타날때만다 동일한 동작(스택에 집어놓고 연산하는) 을 반복하는 구조로 되어있다.


while(다음의 연산자나 오퍼랜드가 EOF 아닌동안)

    if(숫자이면)

        push(숫자)

    else if(연산자 이면)

        pop(오퍼랜드)

    연산을행하고

        push(결과)

    else if(새로운 )

        스택의가장위의것을 pop,push

    else

        에러


스택에 집어넣고 꺼내는 연산은 단순한 동작이지만 에러를 찾아내어 수정하는 데 상당한 시간이 소요되므로 전체 프로그램에서 그 동작을 반복시키는 것보다 그들을 별개의 함수로 만드는 편이 훨씬 편리하다. 그리고 다음 입력 연산자 또는 오퍼랜드를 끄집어 내는 별도의 함수가 필요하다.

또 하나의 중요한 문제는 스택이 어디에 잇는가 하는 점 즉 어느 루틴에서 직접 스택을 엑세스 하는가이다. 한가지 방법은 스택을 main에 두고 스택을 집어넣고 뽑아내는 연산을 하는 루틴에게 스택과 현재의 스택위치를 전달하는 방법이다 그러나 main은 스택을 제어하는 변수들에 관해서는 알 필요가 없고 그택에 집어넣고 꺼내는 동작만을 생각해야 한다. 따라서 스택과 스택에 대한 정보는 push 또는 pop 함수들을 통해서만 액세스 되고 main에 서는 엑세스 되지 않도록 외부변수들을 만들어 보자. 이상과 같은 내용을 프로그램화 하는것은 어렵지 않은데 구조는 다음과 같다.


#include

#define 


function declarations for main


main() { ... }


exrernal variables for push and pop


void push(double f){ ... }

double pop(void) P { ... }


int getop(char s[]){ ... }


routines called by getop


이 프로그램을 여러개의 소스 파일로 분리하는 것에 대해서 나중에 설명하기로 한다. main 프로그램은 기본적으로 연산자와 오퍼랜드와 형에 의한 커다란 switch 이다 이것은 3.4 절에서 보았던 좀더 전형적인 switch 사용의 예제이다.


#include<stdio.h>

#include<stdlib.h>


#define MAXTOP 100

#define NUMBER '0'


int getop(char []);

void push(double);

double pop(void);


/* reverse polish calculator */


int main()

{

    int type;

    double op2;

    char s[MAXTOP];

    while ((type = getop(s)) != EOF) {

        switch (type){

            case NUMBER:

                push(atof(s));

                break;

            case '+':

                push(pop() + pop());

                break;

            case '*':

                push(pop() * pop());

                break;

            case '-':

                op2 = pop();

                push(pop() - op2);

                break;

            case '/':

                op2 = pop();

                if (op2 != 0.0)

                    push(pop()/op2);

                else

                    printf("error: zero divisor\n");

                break;

            case '\n':

                printf("\t%.8g\n",pop());

                break;

            default:

                printf("error: unkown command %s \n",s);

                break;

        }

        

    }

}


+ 와 *의 연산자들은 오퍼랜드의 순서에 상관이 없으므로 pop되는 순서에 상관이 없지만 -나 /연산자들은 왼쪽 오퍼랜드와 오른쪽 오퍼랜드의 순서가 명확히 정해져야 한다.


push(pop() - pop() );  /* wrong */


순서를 명확히 하기 위해 첫번째 값을 스택에서 꺼내어 임시 변수에 저장할 필요가 있다.


#define MAXVAL 100     /* maximum depth of val stack */


int sp = 0;            /* next free stack position */

double val[MAXVAL];    /* value stack */


/* push: push f onto value stack */

void push(double f)

{

    if(sp<MAXVAL)

        val[sp++] = f;

    else

        printf("error: stack full,can't push %g\n",f);

}


/* pop : pop and return top value from stack */


double pop(void)

{

    if(sp>0)

        return val[--sp];

    else {

        printf("error: stack empty\n");

        return 0.0;

    }

}


함수 밖에서 선언된 변수는 외부형 변수이다 그러므로 push와 pop에 의해서 공유되어야 하는 스택과 스택 포인터는 함수 바깥에서 정의 되어야 한다. main 자체는 스택과 스택포인터를 참조하지는 않느다(스택과 스택포인터에 관한 정보는 숨겨져있다)

이제 getop함수를 구현해보자 이 함수는 다음 연산자나 오퍼랜드를 가져오는 기능을 수행한다. 이함수는 공백(blank)과 탭(tap)을 무시하고 다음 문자나 숫자가 소수점이 아닌 경우 그것을 리턴시킨다. 그렇지 않으면 숫자나 소수점으로 구성되는 숫자열을 구성해서 NUMBER 라는 변수로 그값을 리턴시킨다


#include<ctype.h>


int getch(void);

void my_ungetch(int);


/* getop: get next operator or numeric operand */


int getop(char s[])

{

    int i,c;

    while ((s[0] = c= getch()) ==' ' || c == '\t' )

        ;

    

    s[1] = '\0';

    if(!isdigit(c) && c != '.')

        return c;           /* not a numer */

    

    i=0;

    if (isdigit(c))

        while (isdigit(s[i++] = c = getch()))

            ;

    if (c == '.')

        while (isdigit(s[++i] = c= getch()))

            ;

    s[i] = '\0';

    

    if (c != EOF)

        my_ungetch(c);

    

    return NUMBER;

}


getch 함수와 my_getch 함수란 무엇인가 입력 프로그램은 지나치게 입력되지 전까지는 충분히 읽었는지 판탄할수없다 그 예로 숫자를 구성한느 문자들의 집합을 들수 있다. 즉 숫자가 아닌것이 입력되기 전까지는 숫자 입력이 끝났는지 아닌지를 알수없다. 이 문제를 해결하기 위해서는 일단 한 문자를 읽은 후 숫자가 아니면 그것을 원상태로 되돌려 주어야 한다. 다시 말해서 프로그램이 한 문자를 읽어 들일 때마다 그것을 입력에 다시 넣어서 프로그램의 나머지 부분들은 그것이 읽히지 않았던 것처럼 처리하면 되는것이다.

다행히도 한쌍의 함수를 사용함으로써 위의 동작을 하도록 할수있다. getch는 다음 입력문자를 읽어들인다 my_getch 는 문자를 다시 입력에 넣어서 다음에 getch를 호출할때 그 것을 넘겨주도록 한다. 이것들의 동작은 아주 간단한다 getch 는 버퍼에 어떤 문자가 있으면 읽어 들이고 버퍼가 비면 getch를 호출한다. 그리고 버퍼 안의 현재 문자 위치를 기록해 두어야 한다(인덱스) 버퍼와 인덱스틑 getch 와 my_getch에 의해 공유되며 호출할 때 그드르이 값을 유지해야 하므로 모두 외부형으로 선언되어야 한다.그러므로 getch와 my_getch그리고 그들의 공유된 변수는 다음과 같다.


#define BUFSIZE 100


char buf[BUFSIZE]; /* buffer for ungetch */

int bufp = 0;      /* next free position in buf */


int getch(void)    /* get a (possibly pushed back) character */

{

    return (bufp>0) ? buf[--bufp] : getchar();

}


void ungetch(int c)

{

    if (bufp>= BUFSIZE)

        printf("ungetch: too many character\n");

    else

        but[bufp] = c;

}


ungetch는 한 문자를 리턴해 주는 기능을 가진 표준라이브러리에 있는 함수인데 제7장에서 자세히 설명하겠다 읽어 들인 문자를 다시 집어넣기 위해 하나의 문자 대신에 문자 배열을 사용했는데 이는 일번적인 경우를 다를수 잇도록 하기 위함이다.


예제 4-3 계산기 프로그램에 modulus 연산자(%)와 음수 계산기능을 첨가하라.


예제 4-4 pop 연산을 거치지 않고 스택의 제일 위쪽 요소를 화면에 출력하는 명령을 추가하라 스택을 비우는 명령도 추가하라


예제 4-5 sin,exp,pow 등과 같은 라이브러리 함수를 사용할수 있도록 프로그램을 수정하라.(부록 b참조)


예제 4-6 변수를 취급할수 있도록 프로그램을 수정하라(하나의 문자로 된 26개의변수를 설정하는것이 쉬울것이다)


예제 4-7 전체 문자열 s룰 input으로 되돌려주는 ungets(s) 루틴을 작성해 보라 ungets루틴이 ungetch를 사용해야 하는가? 또한 buf와 bufp에 대한 정보가 있어야 한느가?


예제 4-8 리턴해줄 문자가 단 하나인 경우 getch와 ungetch 를 수정하라.


예제 4-9 우리가 작성한 getch나 ungetch는 EOF pushed-back을 처리하지 못한다. 가능하도록 하려면 어떻게 해야하는지 구상하여 프로그램을 작성하라.


예제 4-10 입력 라인 전체를 읽어들이는 getline을 사용하면 getch와 ungetch는 필요 없어진다. getline을 사용하여 계산기 프로그램을 다시 작성하라