함수 내에서 선언된 변수가 함수의 밖에서 선언된 변수와 분명히 구분된다는 것을 설명했다. 이런 사실은 C 프로그래밍에서 중요한 변수의 범위(variable scope)를 부분적으로 설명하는 예이다.

·변수의 범위
·외부 변수의 의미와 외부 변수를 사용하지 않아야 하는 이유
·지역 변수에 대해서
·정적 변수와 자동 변수의 차이
·지역 변수와 블록에 대해서
·적절한 변수의 형태를 선택하는 방법

1. 변수의 범위란?
 : 변수의 범위(scope)는 프로그램에서 변수를 사용할 수 있는 범위나 또는 변수가 프로그램 내에서 효과를 나타낼 수 있는(visible) 범위를 말한다. C에서 변수에 대해서 설명할 때에는 변수의 값을 사용할 수 있는 가능성(accessibility)과 주어진 범위 내에서 효과를 나타내는 것을 뜻하는 유효성(visibility)을 함께 사용한다. 변수의 범위를 언급할 대 변수(variable)는 C의 모든 데이터형을 뜻한다. 즉, 간단한 변수에서부터 배열, 구조체, 포인터 등을 모두 포함한다. 또한, const 키워드로 정의된 기호 상수도 포함한다. 변수의 범위는 변수의 생명, 즉 메모리에서 변수의 값이 보존되는 기간이나 변수에 저장 영역이 할당되고 해제되는 시기에 영향을 준다. 우선, 변수의 유효성(visibility)에 대해서 알아보도록 하자.

1.1 변수의 범위를 설명하는 예제
 : <리스트 12.1>에 있는 프로그램을 살펴보자. 프로그램은 5번째 줄에서 변수 x를 정의하고, 11번째 줄에서 x의 값을 출력하기 위해서 printf() 함수를 호출하며 다시 x의 값을 출력하기 위해 printf_value()를 호출한다. 함수 print_value()에는 x의 값이 인수로 전달되지 않는다는 것에 주의하기 바란다. 이 함수는 19번째 줄에서 printf() 함수에 대한 인수로 x를 사용하고 있다.

<리스트 12.1> 변수 x는 함수 print_value내에서도 사용될 수 있다.

 /* 변수의 범위를 설명하는 에제 */


 #include <stdio.h>


 int x = 999;


 void print_value(void);


 main()

 {

    printf("%d\n", x);

    print_value();


    return 0;

 }


 void print_value(void)

 {

    printf("%d\n", x);

 }

-> 출 력
  999
  999

이 프로그램은 아무런 문제 없이 컴파일되고 실행된다. 이제 이 프로그램에서 x를 정의하는 문장을 main() 함수 내의 위치로 이동시켜 보자. 변경된 소스 코드는 <리스트 12.2>에 나타나 있다.

<리스트 12.2> 변수 x는 함수 print_value 내에서 사용될 수 없다.

 /* 변수의 범위를 설명하는 예제 */


 #include <stdio.h>


 void print_value(void);


 main()

 {

    int x = 999;


    printf("%d\n", x);

    print_value();


    return 0;

 }


 void print_value(void)

 {

    printf("%d\n", x);

 }

<리스트 12.2>를 컴파일하면 컴파일러는 다음과 비슷한 에러 메시지를 출력할 것이다.

   list1202.c[19] : Error: undefined identifier 'x'.

에러 메시지에서 괄호 내에 나타나는 숫자는 에러가 발생한 프로그램의 문장 번호를 뜻한다. 19번째 줄은 print_value() 함수 내에서 printf() 함수를 호출하는 문장이다. 에러 메시지는 print_value() 함수 내에서 변수 x가 '정의되지 않았다.'는 것을 알려준다. 즉, 함수 내에서는 변수 x가 유효하지 않다. 그러나 11번째 줄에 있는 printf()함수에서는 에러 메시지가 발생하지 않는다는 것에 주목할 필요가 있다. 프로그램의 11번째 줄에서는 변수 x가 유효한 것이다.

<리스트 12.1>과 <리스트 12.2>의 유일한 차이는 변수 x가 정의된 위치이다. x를 정의하는 문장을 이동시키면 변수의 범위가 바뀐다. <리스트 12.1>에서 x는 외부(external) 변수로 선언되었고, 이때 변수의 유효 범위는 전체 프로그램이다. 변수 x는 main() 함수와 print_value() 함수에서 모두 사용할 수 있다. 그러나 <리스트 12.2>에서 x는 지역(local) 변수로 선언되었고, 이때 변수의 유효 범위는 main() 함수로 제한된다. 그래서 함수 print_value()에서는 x가 존재하지 않는 것으로 간주된다 지역 변수와 전역 변수에 대해서는 나중에 상세히 설명할 것이므로 여기서는 변수 범위의 중요성을 알아둘 필요가 있다.

1.2 변수의 범위가 중요한 이유
 : 변수의 범위가 중요하다는 것을 이해하기 위해서는 구조화 프로그래밍에 대해서 다시 한 번 언급할 필요가 있다. 구조화 프로그래밍에서는 특정 작업을 수행하는 독립된 함수로 프로그램을 분할한다. 여기서 중요한 것은 독립적(independent)이라는 단어이다. 완전히 독립된 함수를 작성하기 위해서는 함수 내의 변수가 다른 함수에 의해서 간섭되지 않아야 한다. 함수 내에서 독립된 변수를 사용하면 프로그램의 다른 부분에 영향을 받지 않고 주어진 작업을 정상적인 상태로 수행하는 함수를 작성할 수 있다. 여기서, 함수들 간에 완전히 독립된 데이터를 사용하는 것이 항상 바람직하다는 것을 짐작할 수 있을 것이다. 사용되는 변수의 범위를 지정하면 함수들간에 독립된 데이터를 사용할 수 있다. 변수의 범위를 지정하는 것은 이처럼 함수의 독립성에 영향을 준다.

2. 외부 변수
 : 외부(external) 변수는 어떤 함수의 바깥에서 정의되는 것이다. main() 함수도 하나의 함수이므로 외부 변수는 main()의 박에서 선언되는 것을 포함한다. 외부 변수는 가금 전역(global) 변수라고도 한다.

2.1 외부 변수의 범위
 : 외부 변수의 범위는 전체 프로그램이다. 그래서 외부 변수는 main()이나 프로그램 내의 다른 모든 함수에서 사용될 수 있다. 예를 들어, <리스트 12.1>에 있는 변수 x는 외부 변수다. 프로그램을 컴파일하고 실행하여 살펴보았듯이 x는 두 개의 함수 main()과 print_value()에서 유효하다. 그러나 정확히 말해서 외부 변수의 유효 범위가 전체 프로그램이라는 사실은 잘못된 것이다. 외부 변수의 범위가 변수를 정의하는 전체 소스 코드 파일이라는 표현이 더 정학하다. 전체 프로그램이 하나의 소스 코드 파일로 구성된다면 두 가지 범위는 같다. 대부분의 간단한 C 프로그램은 하나의 파일로 구성되고 프로그램도 이렇게 하나의 파일로 구성된다.

2.2 외부 변수를 사용하는 시기
 : 몇 가지 예제 프로그램에서 외부 변수를 사용하고 있지만 실제로는 외부 변수를 사용하지 않도록 해야 한다. 그 이유는 무엇일까? 외부 변수를 사용하면 구조화 프로그래밍에서 중심이 되는 모듈의 독립성(modular independence)이 사라지게 된다. 모듈의 독립성은 프로그램을 구성하는 각각의 함수나 모듈이 주어진 작업을 수행하기 위해서 필요로 하는 모든 코드와 데이터를 내부에 포함하고 있는 것을 뜻한다. 지금까지 사용된 간단한 프로그램에서는 이런 모듈의 독립성이 중요하지 않게 생각될 수도 있지만, 더 크고 복잡한 프로그램을 작성하게 되면 외부 변수에 대한 함수의 의존성은 중요한 문제가 될 수 있다. 그렇다면 외부 변수는 어떤 경우에 사용해야 할까? 프로그램을 구성하는 대부분의 함수나 또는 모든 함수가 사용해야 하는 변수를 선언하는 경우에만 외부 변수를 사용하는 것이 좋다 const 키워드를 사용하여 정의되는 기호 상수는 외부 변수로 선언하기에 적합한 것이다. 변수가 몇 개의 함수에서만 사용된다면 외부 변수로 선언하지 말고 함수에 인수로 전달하자.

2.3 extern 키워드
 : 함수에서 외부 변수를 사용할 필요가 있을 때에는 extern 키워드를 사용하여 함수 내에서 변수를 선언하는 것이 좋다. 변수는 다음과 같은 형식으로 선언된다.

  extern type name;

type은 변수의 형태이고, name은 변수의 이름이다. 예를 들어, <리스트 12.1>에 포함디어 있는 함수 main()과 print_value()에는 변수 x를 선언하는 문장을 포함시킬 수 있다. <리스트 12.3>에는 수정된 프로그램이 나타나 있다.

<리스트 12.3> 변수 x는 함수 main()과 print_value에서 외부 변수로 선언된다.

 /* 외부 변수 선언 */


 #include <stdio.h>


 int x = 999;


 void print_value(void);


 main()

 {

    extern int x;


    printf("%d\n", x);

    print_value();


    return 0;

 }


 void print_value(void)

 {

    extern int x;

    printf("%d\n", x);

 }

3. 지역 변수
 : 지역 변수(local variable)는 함수 내에서 정의되는 변수이다. 지역 변수의 범위는 변수가 정의된 함수로 제한된다. 다섯번째 강의 "함수의 기본"에서 함수 내에서 사용되는 지역 변수, 지역 변수를 정의하는 방법, 지역 변수의 장점에 대해서 설명했다. 지역 변수는 컴파일러에 의해서 자동으로 0의 값으로 초기화되지 않는다. 지역 변수를 정의할 때 변수를 초기화하지 않으면 지역 변수에는 임의의 값(garbage)이 저장된다. 그래서 지역 변수를 사용하기 전에는 반드시 필요한 값을 저장해야 한다. 또한, main() 함수 내에서도 지역 변수를 사용할 수 있다. <리스트 12.2>에 있는 변수 x는 main() 함수에서 사용되는 지역 변수의 예이다. 변수 x는 main() 내에서 정의되고 프로그램의 결과로도 알 수 있듯이 main() 내에서만 유효하다.

3.1 정적 변수와 자동 변수
: 지역 변수는 기본적으로 자동 변수이다. 자동 변수는 함수가 호출될 때마다 새롭게 생성되고 함수의 실행이 끝나면 사라지는 변수를 뜻한다. 좀더 정확히 표현하자면 자동 변수는 변수가 정의되어 있는 함수가 호출될 때마다 변수의 값을 보존하지 않는다는 것을 뜻한다. 지역 변수 x를 사용하는 함수가 프로그램에서 호출된다고 하자. 또한, 함수가 처음 호출될 때 변수 x에는 100의 값이 할당된다고 가정하자. 함수의 동작이 안료되면 제어는 다시 함수를 호출한 프로그램으로 돌아가고 함수는 나중에 다시 호출된다. 변수 x는 여전히 100의 값을 가지고 있을까? 그렇지 않다 처음에 생성되었던 변수 x는 제어가 프로그램으로 다시 전달될 때 사라졌다. 함수가 다시 호출될 때에는 변수 x가 새롭게 생성된다. 과거의 x는 완전히 사라진다. 그렇다면 함수가 호출될 때마다 지역 변수의 값을 보존해둘 필요가 있을 때에는 어떻게 할 것인가? 예를 들어, 데이터를 인쇄하는 함수는 새로운 페이지로 진행해야 하는 시기를 결정하기 위해서 이미 프린터로 전송된 줄(line)의 수를 기억할 필요가 있을 것이다. 함수가 호출될 때 계속해서 지역 변수의 값을 보존하기 위해서는 static 키워드를 사용하여 변수를 정적(static) 변수로 정의해야 한다. 예를 들어, 다음과 같이 할 수 있다.

   void func1( int x )
   {
      static int a;
      /* 추가적인 코드 */
   }

<리스트 12.4>에 있는 프로그램은 자도 변수와 정적 지역 변수 간의 차이를 보여준다.

<리스트 12.4> 자동 변수와 정적 지역 변수 간의 차이를 보여준다.

 /* 자동 변수와 정적 지역 변수의 사용 예 */

 #incude <stdio.h>


 void func1(void);


 main()

 {

    int count;


    for(count = 0; count < 20; count++)

    {

       printf("At iteration l%d: ", count);

       func1();

    }


    return 0;

 }


 void func1(void)

 {

    static int x = 0;

    int y = 0;


    printf("x = %d, y = %d\n", x++, y++);

 }

프로그램은 다음과 같은 결과를 보여준다. 정적 변수로 선언된 x는 함수가 호출될 때 값을 보존하므로 변수의 값은 계속해서 증가한다. 반면에, 자도 변수로 선언된 y는 함수가 호출될 때마다 0으로 다시 초기화된다. 또한, 이 프로그램은 두 가지 형태의 변수에서 초기화가 수행되는 방법의 차이점을 보여준다. 즉, 변수가 정의되는 것과 초기화되는 시기에 대해서 알려준다. 정적 변수는 단지 함수가 처음 호출될 때에만 초기화된다. 나중에 다시 함수가 호출될 때 프로그램은 정적 변수가 이미 초기화되었다는 사실을 기억하고 있으므로 다시 초기화하지 않는다. 변수는 함수가 최근에 실행되었을 때 저장되어 있던 값을 그대로 가지게 된다. 이와는 대조적으로, 자동 변수는 함수가 호출될 때마다 지정된 값으로 다시 초기화된다. 자동 변수를 사용해보면 지금까지 설명한 내용을 충분히 이해할 수 있을 것이다. 예를 들어, <리스트 12.4>에 있는 프로그램에서 두 개의 지역 변수가 초기화되지 않도록 변경하면 함수 func1()은 다음과 같은 내용이 될 것이다.

  void func1(void)
  {
     static int x;
     int y;

     printf("x = %d, y = %d\n", x++, y++);
   }

프로그램을 이렇게 변경하여 실행하면 y의 값은 함수가 호출될 때마다 증가한다는 사실을 알 수 있다. 함수가 호출될 때 y의 값이 계속 보존된다는 것을 알 수 있는 것이다. 그렇다면 지금까지 자동 변수에 대해서 설명했던 내용은 잘못된 것일까? 절대로 그렇지 않다. 지금까지 설명한 내용은 모두 사실이다. 앞에서 설명한 내용과 프로그램의 실행 결과를 보고 알 수 있듯이, 함수가 반복적으로 호출될 동안 자동 변수가 값을 보존하는 것은 단지 우연한 일에 불과하다. 좀더 구체적으로 살펴보자. 함수가 호출될 때마다 새로운 y가 생성된다. 컴파일러는 이전에 함수가 호출되었을 때 사용하던 곳과 동일한 메모리 영역을 새로운 y에 할당하여 사용할 것이다. y가 함수 내에서 분명하게 초기화되지 않는다면 메모리의 저장 영역에는 y가 이전에 가지고 있던 값이 저장되어 있을수 있다. 그래서 변수는 계속해서 값을 보존하는 것처럼 보일 수 있지만, 사실은 우현한 일치에 불과한 것이다. 이렇게 같은 값을 가지게 되는 상황이 항상 발생한다고 볼 수는 없는 것이다.
 지역 변수는 기본적으로 자동 변수의 형태를 가지므로 변수를 정의할 때 지정할 필요가 없다 그러나 필요하다면 다음과 같이 변수의 데이터형을 표현하는 키워드 앞에 auto 키워드를 추가할 수 있다.

   void func1(int y)
   {
      auto int count;
      /* 그 밖의 프로그램 문장 */
   }

3.2 매개 변수의 범위
 : 함수의 헤더에서 매개 변수로 사용되는 변수는 지역(local) 변수와 같은 유효 범위를 가진다. 예를 들어, 다음 함수를 살펴보자.

   void func1(int x)
   {
      int y;
      /* 그 밖의 프로그램 문장 */
   }

 x와 y는 모두 함수 func1() 내에서만 유효하게 사용되는 지역 변수이다. 물론, x는 처음에 함수를 호출한 프로그램에서 전달되는 어떤 값을 가진다. 이렇게 전달된 값을 사용할 때에는 x를 다른 어떤 지역 변수와 같은 방법으로 사용할 수 있다. 매개 변수는 항상 대응하는 인수에 의해서 전달되는 값을 가지게 되므로 static이나 auto로 지정하는 것은 아무런 의미가 앖다.

3.3 외부 정적 변수
 : 외부 변수를 정의할 때 static 키워드를 포함시키면 정적 변수로 말들 수 있다.

    static float rate;
    main()
    {
      /* 그 밖의 프로그램 문장 */
    }

일반적인 외부 변수와 정적 외부 변수와의 차이는 유효 범위에 있다 .일반적인 외부 변수는 파일 내에 포함되어 있는 모든 함수에서 유효하고 다른 파일에 포함되어 있는 함수에 의해서도 사용될 수 있다. 정적 외부 변수는 단지 변수가 정의된 파일 내에서 변수가 정의된 부분 이후에 있는 함수에만 유효하다.

3.4 레지스터 변수
 : register 키워드는 자동 지역 변수가 메모리 대신에 프로세서의 레지스터에 저장되도록 컴파일러에게 지시하는 데 사용된다. 프로세서의 레지스터(processor register)는 무엇이고 레지스터를 사용할 때의 장점은 무엇일까? 컴퓨터의 CPU(central processing unit)에는 레지스터라는 몇 개의 데이터 저장 영역이 존재한다. 덧셈이나 뺄셈과 같은 실제 데이터 연산은 CPU의 레지스터에서 수행된다. CPU는 데이터를 처리하기 위해서 메모리 내에서 레지스터로 값을 읽어들이고 처리된 결과를 다시 메모리에 저장한다. 데이터를 메모리에서 읽어들이고 다시 메모리로 저장하는 일련의 과정에서는 약간의 시간이 소모된다. 만약 어떤 변수의 값을 레지스터에 저장하여 사용한다면 변수의 값을 더 빨리 사용할 수 있을 것이다.

자동 변수를 정의할 때 register 키워드를 포함시키면 변수를 레지스터에 저장하도록 '요쳥' 할 수 있다. 다음 예제를 살펴보자

   void runc1(void)
   {
      register int x;
      /* 그 밖의 프로그램 문장 */
   }

앞에서 '요청'이라는 단어를 사용했다는 것에 주의하기 바란다. 프로그램의 상태에 따라 변수의 값을 저장하기 위한 레지스터가 남아 있지 않을 수도 있다. 이때 컴파일러는 변수를 일반적인 레지스터 변수가 아니라 일반적인 자동 변수로 취급할 것이다. register 키워드는 명령하는 것이 아니라 일반적인 자동 변수로 취급할 것이다.

register 키워드는 명령하는 것이 아니라 '요청'하는 것이다 .레지스터 변수의 가장 큰 장점은 순환문의 카운터와 같이 함수 내에서 자주 사용되는 변수에서 증명된다. register 키워는 배열이나 구조체가 아니라 간단한 숫자 변수에 대해서만 사용될 수 있다. 또한 , 정적 변수나 외부 변수로 사용할 수 없다. 레지스터 변수에 대한 포인터를 정의하는 것도 불가능하다.

4. 지역 변수와 main() 함수
 : 지금가지 설명한 내용은 모두 main()과 다른 함수에 적용되는 사항이다. 정확히 말하면, main()은 다른 모든 함수와 마찬가지로 하나의 함수이다. main() 함수는 프로그램이 운영체제에서 실행될 때 호출되고 프로그램이 종료될 때 제어는 main()에서 운영체제로 돌아간다. 이것은 main()에서 정의된 지역 변수가 프로그램이 시작될 때 생성되고 프로그램이 종료될 때 사라진다는 것을 뜻한다. 그래서 main() 함수가 호출될 때 값을 보존하는 static 상태의 지역 변수를 생성하는 것은 아무런 의미가 없다.

변수는 프로그램이 실행될 때마다 값을 보존할 수 없다. 결과적으로, main() 함수에서는 자동 변수와 정적 지역 변수의 차이가 없다. main() 내에서는 물론 static 키워드를 사용하여 지역 변수를 정의할 수 있지만 아무런 효과를 나타내지 않는다.

# 잠깐 노트
 ·main()은 다른 모든 함수와 비슷한 구조를 가지는 하나의 함수라는 사실을 기억하자
 ·main() 내에서 정적 변수는 아무런 효과도 나타내지 않으므로 정적 변수를 생성한지 않도록 하자.

5. 어떤 형태의 변수를 생성해야 하는가?
 : 프로그램에서 특정 변수의 형태를 결정해야 할 때에는 다음 도표를 참고하면 도움이 될 것이다. <표 12.1>에는 C에서 사용할 수 있는 5가지 종류의 변수 형태가 나타나 있다.

<표 12.1> C 5가지 변수 형태

종 류

키워드

지속 시간

정의되는 위치

유효 범위

자동 변수

없음¹

일시적

함수 내부

지역적

정적 변수

static

일시적

함수 내부

지역적

레지스터 변수

register

일시적

함수 내부

지역적

외부 변수

없음²

지속적

함수 외부

전체적(모든 파일)

정적 외부 변수

static

지속적

함수 외부

전체적(특정 파일)

 ¹auto 키워드는 선택적으로 사용할 수 있다.

 ²extern 키워드는 다른 곳에서 이미 정의된 정적 외부 변수를 선언하기 위해서

   함수 내에서 사용되는 것이다.

변수의 형태를 결정할 때에는 가능하다면 자동 변수를 사용해야 하고, 필요한 경우에만 다른 형태를 선택해야 한다. 다음은 몇 가지 참고 사항이다.

·일단 모든 변수를 자동 지역 변수의 형태로 사용하자.
·변수가 자주 사용된다면 register 키워드를 사용하여 레지스터 변수로 정의하자.
·main()이 아닌 다른 함수 내에서 함수가 호출될 때 변수의 값이 보존되어야 한다면 정적 변수로 정의하자.
·변수가 프로그램의 대부분이나 또는 모든 함수에 의해서 사용된다면 외부 변수로 정의하자

6. 지역 변수와 블록
 : 지금까지 설명한 내용은 단지 함수 내에서 지역 변수를 사용하는 것을 중심으로 했다. 지역 변수는 기본적으로 함수 내에서만 사용되지만 중괄호 내에 포함된 프로그램의 어떤 블록 내에서도 지역적으로 사용되는 변수를 생성할 수 있다. 블록 내에서 사용되는 변수를 선언할 때에는 가장 먼저 변수가 선언되어야 한다는 것을 기억하자. 예를 들어, <리스트 12.5>를 살펴보자.

<리스트 12.5> 프로그램의 블록 내에서 지역 변수 정의하기

 /* 블록 내에서 지역 변수 사용하기 */


 #include <stdio.h>


 main()

 {

    /* main()에 지역적인 변수 정의 */


    int count = 0;


    printf("\nOutside the block, count = %d", count);


    /* 블록의 시작 */

    {

       /* 블록에 지역적인 변수 정의 */


       int count = 999;

       printf("\nWithin the block, count = %d", count);

    }


    printf("\nOutside the block again, count = %d\n", count);

    return 0;

 }

이렇게 지역 변수를 사용하는 것은 C 프로그래밍에서 흔하지 않고 필요하다고 생각되지도 않을 것이다. 실제로, 이렇게 같은 이름의 변수를 지역적으로 사용하는 방법은 프로그램에서 문제점을 찾는 경우에 가장 많이 사용된다. 즉, 프로그램의 일부분을 괄호 내에 포함시켜 일시적으로 독립시키고 잘못된 내용을 찾기 위해서 지역 변수를 사용할 수 있다. 다른 한 가지 장점은 변수가 사용되는 부분과 가까운 곳에서 변수를 선언하고 초기화할 수 있다는 것이다. 이렇게 하면 프로그램을 이해하기 쉽게 만들 수 있다.

'C 언어' 카테고리의 다른 글

10장 문자와 문자열  (0) 2019.06.02
11장 구조체  (0) 2019.06.02
13 장 고급 프로그램 제어문  (0) 2019.06.02
14장 화면, 프린터, 키보드 사용하기  (0) 2019.06.02
15장 포인터 : 고급 기능들  (0) 2019.06.02

+ Recent posts