- 정적 변수
- 키워드로 'static'을 사용한다.
- 자기가 선언된 위치에서만 동작하는 변수이다.
static int g_iStatic = 0;
전역 변수의 경우 한 파일에 이미 선언된 이름을 다른 파일에서 똑같은 이름의 변수로 선언하려 하면 컴파일 단계에서는 문제가 없으나 같은 전역 변수의 이름으로 2개가 존재하기 때문에 링크 단계에서 문제가 생긴다.
정적 변수는 전역 변수와 마찬가지로 데이터 영역을 사용한다. 하지만 두 변수에는 차이가 있다. 정적 변수의 경우 서로 다른 파일에서 같은 이름의 정적 변수를 2개 선언해도 빌드 과정에서 오류가 발생하지 않는다. 정적 변수는 선언되는 위치에 따라 동작에 차이가 있다. 하지만 정적 변수에 해당하는 static 키워드가 가지는 공통적인 의미는 변하지 않는다.
static이란 움직이지 않는 정적인 성질이 있다는 뜻이다. C/C++에서 정적이라는 의미는 변수가 처음 선언된 곳에서만 존재한다는 뜻이다(붙박이장)
그렇기 때문에 밖에서 i라는 static 변수를 찾으려고 해도 찾을 수 없다. 그 자리에 콕 박혀있기 때문이다. 하지만 변수가 선언된 내부 영역에서는 i라는 변수를 찾아낼 수 있다.
만약 func.cpp 파일의 정적변수 i가 main함수 밖 스코프에 있다면 이 정적 변수 i는 func.cpp에 선언된 것이다.
그렇기 때문에 해당 정적 변수는 main함수에서 불러올 수 없다. main 스코프 외의 func.cpp 영역에서만 작동한다.
처음에 전역변수끼리 이름이 겹치면 발생하는 문제가 여기 정적 변수에서는 발생하지 않는다.
전역변수가 똑같은 게 여러 개 있는 건 링크 단계에서 문제가 발생하지만, 정적 변수의 경우 해당 영역에서만 호출 가능하기 때문에 선언된 위치가 다르다면 인식 범위가 달라 같은 이름이라도 구분이 가능한 서로 다른 변수로 여겨진다.
정적 변수가 특정 함수 안에서 선언된 경우, 선언된 함수 내에 변수가 static 키워드를 사용하면서 고정되어 있기 때문에 이 변수는 데이터 영역을 사용한다. 함수가 호출되는 순간 스택 메모리를 사용하는 같은 함수 안에 있는 지역 변수와 달리 정적 변수는 스택 메모리 영역에 포함되어 있지 않게 된다. 따라서 이 변수는 main함수와 같이 해당 함수 외의 함수에서는 호출할 수 없게 된다. 또한 스택 메모리 영역이 아닌 데이터 영역을 쓰기 때문에, 함수 호출이 종료돼도 스택 메모리 해제와 함께 사라지지 않으며 마찬가지로 함수의 호출에 의해 생성되지 않고 계속해서 데이터 영역 메모리에 유지된다.
- 그렇다면 굳이 정적 변수가 필요한 이유는 뭐가 있을까?
정적 변수와 전역 변수의 가장 큰 차이점은 정적 변수는 다른 함수에서 쓸 수 없다는 것이다. 즉 정적 변수의 의미는 기능을 제한했다는 것이다. 이런 특성은 다음과 같은 상황에서 유용하게 사용될 수 있다.
- 해당 변수에 대한 접근을 제한해 유일성을 주고 싶은경우
유지보수, 안정성, 방어적 코딩을 위해 오류를 방지하는 차원에서 정적 변수를 사용한다. 이 경우 다른 사람들이 해당 변수에 수정하거나 접근할 수 없다.
또한 다음과 같은 상황에서도 사용될 수 있다.
- 함수의 호출과 종류에 관계없이 프로그램이 실행되는 동안 유지시킬 수 있는 변수를 만들고 싶은 경우
정적 변수는 데이터 영역에 저장되어 프로그램이 종료될 때까지 계속해서 메모리에 유지되므로 이러한 특성이 필요할 때 활용 된다. - 정적 변수에 대한 오해
- 함수 안에 정적 변수 초기화문이 있으면 함수를 호출할 때마다 초기화될까?
아니다. 다음의 예를 통해 알 수 있다.
static int g_iStatic = 0;
int Test()
{
static int i = 0;
++g_i;
++i;
return i;
}
int main()
{
g_iStatic;
Test();
Test();
Test();
Test();
int iCall = Test();
g_i = 0;
printf("Test 함수 호출 횟수 : %d\n", iCall);
return 0;
}
코드를 실행해 보면 iCall의 값이 5가 된다. 즉, Test() 함수가 호출될 때마다 전역 변수를 0으로 초기화하는 것이 아니라는 뜻이다. 이는 정적 변수의 문법적인 특징이다. 함수 안에 정적 변수를 선언해 두면 함수를 호출할 때마다 초기화 값을 가지는 게 아니라, 이 함수에서만 접근 가능한 데이터 영역의 만들어진 정적 변수의 초기값을 0으로 준다는 뜻이다. 즉 컴파일러가 함수를 최초로 한 번 실행했을 때만 정적 변수를 초기화해주고 그다음부터 이 함수가 호출되면 0으로 초기화하는 부분을 건너뛰고 나머지 코드를 실행하게끔 되어 있다.
- #include는 가져온 헤더 파일의 내용을 해당 파일에 복사 붙여 넣기 해준다. 그렇다면 하나의 헤더 파일(common.h)에 정적 변수를 모두 선언해 두고 모든 파일에서 그 헤더를 #include하면 헤더 파일 안의 정적 변수를 모든 곳에서 호출해서 쓸 수 있지 않을까?
아니다. include한 정적 변수는 복사 붙여 넣기 한 파일에서 별개의 변수로 인식되기 때문에 값을 보존할 수 없다. 또한 변수명이 서로 겹치는 문제가 발생한다.
- 그렇다면 모든 파일에서 사용 가능한 통합 전역 변수를 만들고 싶을 땐 어떻게 해야 할까?
아래에서 다룰 외부 변수를 사용한다.
아래의 코드대로 선언하면 중복 선언에 대한 오류가 발생한다.
// common.h
static int g_iStatic = 0;
extern int g_iExtern = 0;
- 왜일까?
외부 함수(extern)는 헤더에 배치할 때 절대 초기화를 넣으면 안 된다. 다음의 방법이 옳은 방식이다.
extern int g_iExtern;
이는 변수를 선언한 것이 아니라 g_iExtern이란 이름의 변수가 있다고 알려주는 역할을 한다. (확정지은 것이 아닌, 있을 것이다 라고 알려주는 용도) 따라서 다른 파일에서 해당 헤더 파일을 include해도 '이러한 변수가 있다'라는 사실만 복붙되므로 문제가 발생하지 않는다.
이때 각 파일들은 이런 외부 변수가 있다는 사실을 알고 있는 상태이기 때문에, 진짜 이 변수가 존재해야 완벽하게 링크 될 것이다. 이 외부 변수는 모든 파일에서 사용 가능한 통합 변수이므로 어느 파일이든 상관없이 아무곳이나 외부 변수를 실제로 초기화하기만 하면 된다.
이러면 단순히 헤더를 include한 상태의 파일에서 헤더 파일 안의 외부 변수를 사용하려고 호출하면 똑같은 변수를 사용하여 값을 수정할 수 있다.
하지만 만약 어떤 파일에서도 외부 변수를 초기화하지 않는 상태에서 변수를 사용하려고 하면 링크 오류가 발생한다.