관리자 글쓰기
C++의 고급 기능
2022. 4. 8. 11:27 - pingu-s

영역

 

변수의 종류와 영역

 

함수 내에 변수와 배열을 선언하는 방법에 관해 학습한다. 변수와 배열의 선언은 지금까지 main() 함수와 자신이 작성하는 함수 내에 기술해왔다는 것을 생각하기 바란다.

int main()
{
	int a; //함수 블록 내에 선언한 지역 변수이다.
	...
}

 

단, 이것은 반드시 함수 내에 수행하지 않으면 안 된다는 것은 아니다. 변수와 배열은 함수의 외부에서 선언할 수 있도록 되어 있다.

int a; //함수 블록 외부에 선언한 전역 변수이다.

int main()
{

	...

}

 

지금까지와 같이 함수 내에 선언된 변수지역 변수(local variable)라고 한다. 한편 함수의 외부에 선언한 변수전역 변수(global variable)라고 부른다.

 

그러면 지역 변수와 전역 변수에는 어떠한 차이가 있는 것일까? 변수의 차이를 살펴보기 위해, 2종류의 변수를 취급하는 코드를 기술해 보도록 한다.

 

예제) 변수의 영역

지역 변수와 전역 변수를 선언하여 변수의 영역을 확인해 보도록 한다.

#include <iostream>
using namespace std;

//func 함수의 선언
void func();

int a = 0; //전역 변수이다.

//main 함수
int main()
{
	int b = 10; //지역 변수이다.

	cout << "main 함수에서는 변수 a와 b가 사용됩니다.\n";
	cout << "변수 a의 값은 " << a << "입니다.\n"; //전역 변수가 사용된다.
	cout << "변수 b의 값은 " << b << "입니다.\n"; //이 함수 내의 지역 변수이다.
	//cout << "변수 c의 값은 " << c << "입니다.\n"; //다른 함수 내의 지역 변수는 사용되지 않는다.
	func();

	return 0;
}

//func 함수의 정의
void func()
{
	int c = 20; //지역 변수이다.

	cout << "func 함수에서는 변수 a와 c가 사용됩니다.\n";
	cout << "변수 a의 값은 " << a << "입니다.\n"; //전역 변수가 사용된다.
	//cout << "변수 b의 값은 " << b << "입니다.\n"; //다른 함수 내의 지역 변수는 사용되지 않는다.
	cout << "변수 c의 값은 " << c << "입니다.\n"; //이 함수 내의 지역 변수가 사용된다.
}

이 코드에서는 다음 3개의 변수를 선언하고 있다.

 

변수 a ...... 함수의 외부에 선언한 전역 변수
변수 b ...... main() 함수 내에 선언한 지역 변수
변수 c ...... func() 함수 내에 선언한 지역 변수

 

변수는 선언한 위치에 따라 코드 내의 어디에서 이용할 수 있는가가 다르다.

우선 지역 변수의 경우는 선언하는 함수 내에서만 이용할 수 있다. 예를 들면, 지역 변수 b는 main() 함수 내에 선언되어 있으므로 func() 함수 내에서는 이용할 수 없다. 역으로 지역 변수 c는 mian() 함수 내에서는 이용할 수 없다. 한편, 전역 변수는 어느 함수에서도 이용할 수 있다. 즉, 변수 a는 main(), func() 함수의 어느 쪽에서도 이용할 수 있다.

변수의 이름이 통용하는 범위를 영역(scope)이라고 한다.

 

  선언 위치 영역
지역 변수 함수의 내부 변수를 선언하는 장소에서 함수의 종료까지 이용할 수 있다.
전역 변수 함수의 외부 어느 함수에서도 이용할 수 있다.

 

지역 변수의 이름 중복

 

변수의 이름과 선언 위치에는 주의하도록 한다. 이를 테면, 똑같은 함수 내의 지역 변수에는 똑같은 이름을 붙일 수 없다. 단, 다른 함수 내에서 선언한 지역 변수에는 똑같은 이름을 붙여도 상관없다.

 

void main()
{
	int a = 0;
	a++;
}
//2개의 지역 변수는 다른 것이다.
int func()
{
	int a = 0;
	a++;
}

상기의 코드에서는 main() 함수와 func() 함수에서 함께 변수 a를 선언하고 있다. 이 2개의 지역 변수는 다른 값을 저장할 수 있고 완전히 별개의 변수를 나타내고 있다. 똑같은 이름이 붙여져도 다른 함수 내의 지역 변수는 별개의 변수인 것이다.

 

전역 변수의 이름 중복

 

전역 변수와 지역 변수는 똑같은 변수명을 사용해도 상관없다. 코드를 살펴보도록 한다.

int a = 0; //전역 변수 a이다.

//main() 함수
int main()
{
	int a = 0; //지역 변수 a이다.
	...
	a++; //인크루드되는 것은 지역 변수 a이다.

	::a++; //인크루드되는 것은 전역 변수 a이다.
}

 

여기서는 전역 변수로서 변수 a를 선언하고 main() 함수 내에서 지역 변수로 변수 a를 선언하였다. 전역 변수와 지역 변수에서는 이와 같이 이름을 중복시킬 수 있다.

단, main() 함수 내에서 a++; 등이라는 기술을 하면 그것은 지역 변수임을 가리킨다. func() 함수 내에서는 지역 변수에 의해 전역 변수의 이름이 숨겨져 있도록 되어 있다. 전역 변수를 사용하려면 ::a++;와 같이 영역 해결 연산자(::)를 사용한다.

 

수명

 

변수의 수명

 

변수의 기억 과정은 다음과 같다.

 

변수를 선언하면 우선 값을 기억하기 위한 상자가 메모리 내에 준비된다(①). 이것을 메모리가 할당된다고 하기도 한다.

그 후, 변수에 값을 저장하거나 출력해서 이용하는 것이지만(②)

마지막에 상자가 해제됨으로써 메모리가 또 다른 용도로 사용되도록 한다(③). 이것을 메모리가 해제된다고도 한다.

 

변수의 상자가 존재하고 값을 기억하고 있는 기간을 수명(lifetime)이라고 한다. 변수가 어떤 수명을 가지는가의 여부는 변수의 선언 위치에도 관계가 있다.

통상의 지역 변수는 함수 내에 선언되었을 때에 변수의 상자가 메모리 내에 준비되고, 함수가 종료할 때에 상자가 해제되어 메모리가 다른 용도로 사용되는 수명을 취한다. 즉, 통상의 지역 변수는 변수가 선언되고 나서 함수가 종료할 때까지의 동안만 값을 저장해둘 수 있다.

한편, 전역 변수는 프로그램의 본체의 처리가 시작하기 전에 한 번만 메모리가 할당되고 프로그램의 종료 시에 메모리가 해제되는 수명을 취한다. 즉, 전역 변수는 프로그램의 시작에서 종료할 때까지의 기간 동안 값을 저장해둘 수 있다.

 

예제) 변수의 기억 수명

지역 변수, 전역 변수, static 변수를 선언하고 변수의 수명을 확인해 보도록 한다.

#include <iostream>
using namespace std;

//func 함수의 선언
void func();

int a = 0; //전역 변수 a를 선언하고 초기화한다.

//main 함수
int main()
{
	for (int i = 0; i < 5; i++)
		func();

	return 0;
}

//func 함수의 정의
void func()
{
	int b = 0; //지역 변수 b를 선언하고 초기화한다.
	static int c = 0; //static을 붙인 지역 변수 c를 선언하고 초기화한다.

	cout << "변수 a는 " << a << "변수 b는 " << b << "변수 c는 " << c << "입니다.\n";

	a++;
	b++;
	c++;
	//각 변수를 인크루드한다.
}

func() 함수는 변수 a, b, c의 값을 출력하고 1개씩 증가하는 함수이다. 전역 변수 a는 프로그램의 시작부터 종료까지 값을 기억하고 있기 때문에 1개씩 값이 증가되어간다.

한편, 지역 변수 b는 함수가 호출될 때마다 처음에 0이 저장되고 함수가 종료할 때마다 상자가 해제된다. 이 때문에 증가를 해도 항상 0이 그대로 되어 있다.

이와 같이 통상의 지역 변수는 함수가 종료할 때까지의 수명밖에 가지지 못한다. 단, 지역 변수에 static이라는 키워드를 지정하면 전역 변수와 똑같은 수명을 가지도록 할 수 있다. 이와 같은 지역 변수를 정적 수명을 가지는 지역 변수라고 한다. 

위 코드에서 변수 c는 정적 수명을 가지는 지역 변수이다. 이 변수는 전역 변수와 똑같이 프로그램의 시작 시에 초기화되고 종료 시에 해제되는 것으로 되어 있다. 즉, func() 함수가 종료해도 상자가 해제되지 않고 값을 유지한 그대로이므로 함수가 호출될 때마다 1개씩 수가 증가되어 간다. static은 기억 클래스 지정자(storage class identifier)라고 부른다.

 

  기억 클래스 수명
지역 변수 (자동) 선언되고 나서 함수가 종료할 때까지(자동)
static 프로그램의 실행 준비시부터 종료할 때까지(정적)
전역 변수   프로그램의 실행 준비시부터 종료할 때까지(정적)

 

또한 변수의 초기화 코드를 기술하지 않은 경우에는 전역 변수와 static 한 지역 변수는 자동적으로 0으로 초기화되어 있다. 한편, 지역 변수를 초기화하지 않는 경우의 초기화는 특히 결정되어 있지 않다.

 

지역 변수로의 포인터와 반환값

 

함수 파트에서 학습한 함수에서는 포인터와 참조를 사용할 수 있다. 그러나 이때는 주의가 필요하다. 다음의 코드를 살펴보도록 한다.

int* func()
{
	int a = 10;
	int* pA = &a; //포인터 pA는 통상의 지역 변수 a를 가리킨다.

	return pA;
}

int main()
{
	int* pRes = func(); //pRes에는 의미가 없는 주소가 저장되어 있다.
						//이와 같은 사용은 할 수 없다.
	...
}

 

여기서는 func() 함수의 반환값을 func 내에 선언한 지역 변수 a를 가리키는 포인터(pA)로 하고 있다.

그러나 func() 함수가 종료했을 때에 통상의 지역 변수인 a는 해제되어 버린다. 이 때문에 호출하는 장소로 되돌아갔을 때에는 변수 a의 주소는 의미가 없는 값으로 되어 버린다. 즉, pRes는 의미가 없는 포인터로 되는 것이다.

이와 같은 pRes를 이용한 코드를 기술하면 치명적인 오류가 발생되는 경우가 있다. 한편, 다음과 같은 반환값을 이용하는 것은 가능하다.

 

#include <iostream>
using namespace std;

int* func()
{
	static int a = 10;
	int* pA = &a; //이 포인터는 정적 지역 변수 a를 가리킨다.

	return pA;
}

int main()
{
	int* pRes = func(); //이와 같은 이용은 가능하다.
	...
}

 

이 코드에서도 함수 내에 선언한 지역 변수로의 포인터를 반환하고 있다. 그러나 이 변수 a는 static을 붙인 정적 변수로 되어 있다. 이 변수는 함수가 종료해도 존재하기 때문에 이 변수로의 포인터를 호출하는 장소에서도 이용할 수 있다.

똑같은 이유로 통상의 지역 변수로의 참조를 반환할 수도 없다. 참조는 변수의 별명에 지나지 않기 때문이다. 변수가 해제되어 있으면 참조도 의미가 없는 것으로 되어 버린다.

 

 

출처: 박흥복서정희. 2015. C++ 프로그래밍 (초보자를 위한). 문운당

'개발 > C++' 카테고리의 다른 글

C++ 열거형, 구조체, 공용체  (0) 2022.05.04
C++의 고급 기능2  (0) 2022.04.11
C++ 배열3  (0) 2022.04.06
C++ 배열2  (0) 2022.04.05
C++ 배열  (0) 2022.04.04