기술자료

DBMS, DB 구축 절차, 빅데이터 기술 칼럼, 사례연구 및 세미나 자료를 소개합니다.

C++ 프로그래밍 :정적 지역 변수(Static Local Variable) 원리

기술자료
DBMS별 분류
Etc
작성자
dataonair
작성일
2015-01-07 00:00
조회
17923



C++ 프로그래밍

정적 지역 변수(Static Local Variable) 원리



C/C++을 비롯한 프로그래밍을 배우게 되면 변수(객체)가 두 종류가 있음을 알게 된다. 하나는 전역 변수고, 또 하나는 지역 변수다.



전역 변수와 지역 변수 각각은 저장되는 메모리 영역과 더불어 생명주기가 다르다는 것을 이미 충분히 알고 있을 것이다. 세상이 명확하게 나눠지는 것으로만 이뤄져있다면 좋겠지만, 늘 불분명한 중간 단계의 것들이 존재해서 이해를 어렵게 하고 골치 아프게 만드는 경향이 있다.


정적 지역 변수가 그런 것 중 하나라고 할 수 있다. 그러나 좋은 점도 있긴 하다. 정적 지역 변수를 제대로 이해한다면, 더 깊은 수준으로 변수의 생성과 초기화에 대해서 이해하게 된다는 점이다. 지금부터 정적 지역 변수를 탐구해보자.



정적 지역 변수의 생성과 초기화

이름에 ‘지역’이란 단어가 포함돼 있어서 지역변수와 비슷할 것이라고 생각하지만, 정적 지역 변수는 사실 전역 변수와 훨씬 가까운 성질을 가지고 있다. 정확히 얘기한다면 정적 지역 변수는 일종의 전역 변수인데, 오직 선언된 블록(보통 함수)에서만 접근 가능한 전역 변수라고 할 수 있다.


지역 변수의 경우 변수가 선언된 함수가 시작될 때 스택에 변수의 메모리 영역이 할당된다. 또한 함수가 끝나는 시점에 할당된 스택은 무효화되기 때문에 지역 변수도 사라진다고 할 수 있다. 그에 비해 전역 변수는 프로세스가 시작될 때 가상 메모리의 데이터 영역에 생성되고, 프로세스가 종료될 때 사라진다. 여기서 중요한 점은 바로 정적 지역 변수도 전역 변수처럼 프로세스가 시작될 때 데이터 영역에 생성되고, 프로세스가 종료될 때 소멸된다는 것이다. 즉, 함수가 시작될 때 생성되는 게 아니라는 것이다.

<리스트 1> 정적 지역 변수의 초기화 1void Func()
{
/*
push ebp // ①
mov ebp,esp // ②
*/ static int s_Val = 1; // ⓐ

/*
pop ebp // ③
ret // ④
*/
}void main()
{
Func();
}


<리스트 1>의 ⓐ를 살펴보자. 정적 지역 변수 s_Val이 선언 및 정의돼 있으며 1로 초기화된다. 이미 설명했듯이 s_Val은 프로세스가 시작되는 순간에 이미 메모리 영역을 차지하고 있다. 즉, s_Val이 스택에 생성되는 것은 아니라는 의미다.


이제 Func의 내부 구조를 살펴보자. Func가 어떻게 동작하는지를 확인하기 위해 ①~④와 같이 어셈블리를 표기했다. 어셈블리에 낯선 독자도 있을 수 있지만, 포기할 필요는 없다. 아직까진 아주 쉬운 문법만 나오기 때문이다. <리스트 1>에서 제일 중요한 점이 있는데, 바로 어셈블리 어디에도 s_Val을 1로 초기화시키는 코드가 없다는 것이다. 무슨 의미인가 하면 s_Val의 초기화는 함수 Func의 실행과 상관없이 이미 프로세스 시작과 동시에 완료됐다는 것이다.


여기서 무척 혼란을 느끼는 독자도 있을 수 있다. 분명히 배우기로는 정적 지역 변수는 함수 시작시 단 한번만 초기화된다고 알고 있기 때문이다. 물론 대부분은 맞는 말이지만, 늘 그렇듯이 항상 그런 것은 아님을 주의해야 한다.

<리스트 2> 정적 지역 변수의 초기화 2int GetTwo()
{
return 2;
}void Func()
{
static int s_A; // ① 초기화없음
static int s_B = 0; // ② 0 초기화
static int s_C = 1; // ③ 상수초기화
static int s_D = GetTwo(); // ④ 함수초기화 cout < < "s_A: " < < &s_A < < endl;
cout < < "s_B: " < < &s_B < < endl;
cout < < "s_C: " < < &s_C < < endl;
cout < < "s_D: " < < &s_D < < endl;
}void main()
{
Func();
}


정적 지역 변수를 <리스트 2>의 ①~④와 같이 네 가지 유형으로 나눠보았다.



① s_A: 초기화 없음 ② s_B: 0으로 초기화 ③ s_C: 0이 아닌 상수 초기화 ④ s_D: 함수 초기화



먼저 네 가지 유형의 공통점은 이미 설명했듯이 모두 프로세스 시작 시 메모리가 할당된다는 점이다. 즉, 이미 생성은 돼 있다. 더 중요한 점이 있는데 이미 초기화도 돼 있다는 것이다. s_A처럼 명시적인 초기화가 없는 경우는 0으로 초기화되며, s_D처럼 함수 초기화가 이뤄지는 것은 Func 호출 전에 먼저 0으로 초기화 돼 있다는 점이다. 정리하면 프로세스가 시작된 이후 Func가 호출되기 전에 이미 s_A, s_B, s_D는 0으로 초기화 돼 있으며, s_C는 1로 초기화 된다는 것이다.


이제 다른 점을 살펴보자. <리스트 2>에서 각 정적 지역 변수의 주소를 출력해보았다. 출력 결과는 다음과 같다(필자의 PC에서 나온 결과이다. 따라서 각 테스트 환경마다 달라질 수 있다).



s_A: 0176923C s_B: 01769238 s_C: 00F690A8 // ⓐ s_D: 01769240



대부분 느낄 수 있겠지만 오직 1로 초기화한 s_C의 메모리 주소가 다른 변수와는 다르다는 것을 확인할 수 있을 것이다. 바로 메모리 영역이 다르기 때문이다. 전역 변수나 정적 변수가 저장되는 데이터 영역은 두 가지로 분리된다. 바로 초기화된 데이터 영역과 초기화되지 않은 데이터 영역이다. 보통 Data영역과 BSS(Block Stated Symbol)영역이라고 부른다.


BSS영역에는 초기화되지 않은 전역 변수나 정적 변수가 위치하는데, 프로세스 시작 시 가장 기본이 되는 값으로 초기화가 이뤄진다. 가장 기본이 되는 값이란 바로 0(NULL)을 나타낸다. 그에 비해서 Data영역에는 기본 값 0이 아닌 상수로 초기화 되는 전역 변수나 정적 변수가 위치하게 된다. Data영역의 값은 이미 실행 이미지(보통 exe)에 기록되어 있기 때문에 프로세스 시작 시 실행 이미지로부터 그대로 복사된다는 특징이 있다.


따라서 0이 아닌 특정한 값으로 초기화되는 s_C는 Data영역에 자리 잡고 있는 것이며, 초기화를 하지 않거나 0으로 초기화하는 s_A, s_B, 그리고 함수 초기화를 하는 s_D는 BSS영역에 자리 잡게 되는 것이다.


정적 지역 변수의 함수 초기화

이제 가장 특이한 경우를 살펴보자. 바로 s_D와 같은 함수 초기화 정적 지역 변수다. 함수 초기화 정적 지역 변수는 일단 BSS영역에 자리 잡아서 프로세스 시작 시 먼저 0으로 초기화 된다. 그리고 정적 지역 변수가 선언된 함수(Func)가 처음 호출되는 순간에 비로소 초기화 함수(GetTwo)가 실행되면서 실제 초기화가 이뤄지게 되는 것이다. 보통 이런 경우만을 설명하면서 정적 지역 변수는 함수가 처음 실행될 때 초기화된다고 이야기하는 것이다.


정리하면 다음수는 실제로 두 번 초기화가 이루어진다. 프로세스가 시작되면서 기본 값 0으로 초기화가 이루어지고, 변수가 선언된 함수가 처음 호출되는 순간에 다시 한 번 함수 초기화가 이뤄진다. 그 이후 다시는 초기화가 이루어지는 일은 없다. 이제부터는 정적 지역 변수의 함수 초기화에 대해서 집중적으로 살펴보겠다.

<리스트 3> 정적 지역 변수의 함수 초기화int GetOne()
{
return 1;
}void Func()
{
/*
push ebp
mov ebp,esp
*/ static int s_Val = GetOne(); // ⓐ
/*
mov eax,dword ptr [$S2] // ①
and eax,1 // ②
jne EXIT // ③START:
mov ecx,dword ptr [$S2] // ④
or ecx,1 // ⑤
mov dword ptr [$S2],ecx // ⑥
call GetOne // ⑦
mov dword ptr [s_Val],eax // ⑧

END:
mov esp,ebp
pop ebp
ret
*/
}void main()
{
Func();
}


<리스트 3>의 ⓐ에서 볼 수 있듯이 정적 지역 변수 s_Val은 함수 GetOne에 의해서 초기화 된다. 상수 초기화를 하던 정적 지역 변수의 어셈블리 코드가 존재하지 않았던 것에 비해서 함수 초기화를 하는 정적 지역 변수의 어셈블리 코드가 추가되었음을 확인할 수 있다. 새롭게 추가된 부분은 바로 ①~⑧이다. 이 부분이 바로 정적 지역 변수의 함수 초기화를 위한 부분이다. 참고로 예제의 어셈블리는 VC++ 컴파일러가 만들어낸 어셈블리를 독자들이 쉽게 이해할 수 있도록 필자가 적절히 수정한 것이다. 불필요한 부분은 제거했으며, 보기 편하도록 라벨을 붙이기도 했다(참고로 원본 어셈블리의 경우 필자가 해석하는데도 꽤 시간이 걸렸다. 어셈블 리가 조금만 복잡해지면 보는 것 자체가 괴로워지는데 VC++만 해도 무척 양호한 편이다. GCC가 생성해낸 어셈블리는 훨씬 이해하기 어렵다).


예상할 수 있겠지만 예제에 나오는 어셈블리가 하는 동작은 바로 함수가 최초 호출되는 시점에 정적 지역 변수를 단 한번만 초기화시키는 것이다. 아직 어셈블리에 익숙하지 않은 독자들의 경우 이해하기 어려울 수 있으니 배경 설명을 하겠다. 대략 아이디어는 이러하다. 정적 지역 변수 s_Val은 함수 Func가 처음 호출되는 순간에만 GetOne이 호출돼 초기화 돼야 하므로, 초기화 여부를 기록할 전역 변수가 필요하다. ①에서 확인할 수 있겠지만 $S2가 바로 초기화 여부를 나타내는 전역 변수다(변수에 왜 $가 쓰였는지 궁금할 수 있는데, 컴파일러가 만들어낸 보조의 전역 변수라고 생각하면 된다).


$S2는 처음에 0으로 설정돼 있는데, 바로 초기화된 적이 없음을 나타낸다. Func가 호출될 때 $S2를 검사해서 0일 경우 GetOne을 호출해 s_Val을 초기화하고 $S2를 1로 변경한다. 그리고 다시 Func가 호출될 때는 $S2를 검사해서 0이 아닐 경우 GetOne을 호출하는 부분을 건너뛰는 것이다. 바로 그와 같은 실행 흐름이 ①~⑧까지 어셈블리로 표현되는 것이다. 어셈블리를 맛보기 한다는 기분으로 실제로 코드를 분석해보자! 코드는 두 부분으로 나누어진다.



먼저 초기화 여부를 판별하는 부분을 살펴보자!


  ①에서 $S2를 eax에 가져온다. ②에서 eax와 1에 and 연산을 수행한다. and 연산의 결과로 0이 나올 경우 ZF(Zero Flag)라는 플래그 레지스터가 1로 설정된다. ③의 jne는 ZF가 0일 경우에 점프하는 명령어이다. 즉, 정적 지역 변수가 초기화된 적이 없을 경우 $S2는 0이고, 1과 and 연산을 할 경우 결과는 0이 된다. 따라서 ZF가 1로 설정되고, ZF가 1이므로 EXIT로 점프가 일어나지 않으며, START가 수행된다. START는 바로 초기화를 하는 부분이다. 다음으로 초기화를 수행하는 부분을 살펴보자. ④에서 $S2를 ecx에 가져오고, ⑤에서 ecx와 1을 or 연산한다. ecx는 이제 1이 될 것이다. 그리고 ⑥에서 다시 ecx를 $S2에 대입한다. 즉, 초기화 여부를 알려주는 $S2를 1로 변경하는 것이다. 그리고 ⑦, ⑧을 통해서 초기화 함수 GetOne을 호출하고, 그 결과인 eax를 s_Val에 대입하는 것이다.


여기서 의문을 가지는 독자가 있을 수도 있는데, 한 함수에 정적 지역 변수가 여러 개일 경우는 초기화 이력을 나타내는 전역 변수도 여러 개가 필요한가이다. 이 부분은 정적 지역 변수의 함수 초기화를 구현하는 컴파일러마다 다를 수 있지만, 일반적으로 효율을 높이는 방향으로 코드가 작성된다. VC++의 경우 $S2를 최대한 공용으로 사용한다. 편의상 초기화 여부를 0, 1이 설정되는 것으로 설명했지만 실제로 복수개의 정적 지역 변수를 처리하기 위해 비트 단위로 0, 1을 설정한다. 즉, 첫 번째 정적 지역 변수는 $S2의 1번 Bit를, 두 번째 정적 지역 변수는 $S2의 2번 Bit를, ... N 번째 정적 지역 변수는 $S2의 N번 Bit를 사용하는 것이다. 만일 한 함수에 사용되는 정적 지역 변수가 32개($S2는 32Bit)를 넘어갈 경우 더 이상 사용할 Bit가 없어지므로 $S2와 같은 새로운 전역 변수 $S3를 만들어 사용하게 된다.


다시 한 번 기억해야 할 사실이 있다. 위에서 제시한 어셈블리는 VC++ 기준이다. 정적 지역 변수의 초기화 처리 방식은 컴파일러마다 다를 수 있다. 그러나 조금씩 차이가 있을 뿐이지 근본적인 아이디어는 비슷하다. 리눅스에서 사용되는 GCC의 경우도 전역 변수를 사용해 초기화 여부를 체크한다.


정적 지역 변수의 스레드 안정성

필자는 VC++가 만들어내는 정적 지역 변수 초기화 어셈블리를 살펴보면서 무엇인가 부족한 느낌을 받은 적이 있다. 독자들도 한번 해당 어셈블리에 어떤 문제가 있을 수 있는 차분히 생각해보자.

바로 VC++가 만들어낸 정적 지역 변수의 함수 초기화 어셈블리가 다중 스레드 환경에서 안전하지 않다는 사실이다. 즉, 다중 스레드가 함수 Func에 동시에 처음으로 접근할 경우 초기화식이 여러 번 호출되는 문제가 있는 것이다. 어셈블리 어디에도 다중 스레드 안정성을 위해 동기화 처리를 하는 것을 찾아볼 수가 없다.

필자가 현재 최신 컴파일러인 VS2013에서도 조사를 했으나 역시 해당 문제는 고쳐지지 않은 상태다. 이 문제는 Thread-safe function local static initialization이라 불리고 있으며 간단히 줄여서 magic statics라 표현하기도 한다. 아마도 다음 버전에서는 magic statics가 해결될 것이라 생각한다.

참고로 GCC의 경우는 이미 magic statics를 해결한 상태다. 내부적으로 초기화 함수가 호출될 경우 동기화를 수행한다. __cxa_guard_acquire, __cxa_guard_release라는 자체 함수를 이용해 정적 지역 변수 초기화 부분을 동기화 하는데, 해당 함수의 코드를 살펴본 결과 PThread의 동기화 함수인 pthread_mutex_lock, pthread_mutex_unlock이 사용되고 있음을 확인할 수 있었다. 여기까지 살펴보았을 때, 정적 지역 변수가 얼마나 복잡한 과정에 의해서 처리되는지를 확인할 수 있을 것이다. 단지 변수 하나를 위해 추가적인 전역 변수를 필요로 하고, 거기에 동기화 처리까지 하고 있으니 말이다.


정적 지역 클래스 객체의 초기화

지금까지 함수를 통해 정적 지역 변수를 초기화하는 경우에 대해 살펴봤는데, 클래스의 생성자도 초기화 함수에 포함시킬 수 있다. 즉, 정적 지역 변수로 클래스 객체를 사용할 경우, 해당 객체는 생성자를 통해 한 번만 초기화된다는 것이다.

<리스트 4> 정적 지역 클래스 객체의 생성자 초기화class CTest
{
public:
CTest()
{
// ② 초기화 수행
}
};void Func()
{
static CTest s_T; // ①
}void main()
{
Func();
}



①의 정적 지역 변수 s_T는 초기화를 하지 않는 것처럼 보이지만 실제로는 함수 초기화와 같은 과정이 진행된다. s_T는 클래스 CTest 객체기 때문에 ②의 CTest 생성자가 호출되면서 초기화가 이뤄진다.


초기화 순서 제어

정적 지역 변수가 초기화 되는 시점에 대해서 살펴보았는데, 꼭 기억해야할 것이라면 정적 지역 변수가 함수나 생성자를 통해 초기화 되는 시점은 오직 변수가 선언된 함수가 처음 호출되는 순간이라는 것이다. 이런 특징과 대비되는 변수가 바로 전역 변수다. 전역 변수도 당연히 함수나 생성자를 통해 초기화 될 수 있다. 그러나 전역 변수는 함수 안에서 선언된 것이 아니다. 그렇다면 전역 변수의 함수 초기화는 언제 일어나는 것일까 바로 프로세스가 시작되면서 처음 호출되는 함수인 CRT Startup 안에서 초기화가 이뤄진다. CRT Startup이 main 함수를 실행하게 되는데, main 함수가 실행되기 전에 전역 변수의 함수 초기화를 수행하게 된다. 간단하게 초기화 순서를 정리해보자.


프로세스가 시작될 경우 가장 먼저 가상 메모리가 구성되면서 전역 변수와 정적 변수들이 생성된다. 만일 상수로 초기 값을 줄 경우 생성과 동시에 초기 값이 설정될 것이다. 초기 값을 주지 않을 경우 기본 값인 0(NULL)이 설정된다. 함수(생성자 포함)를 통해서 초기화가 이뤄질 경우 정적 지역 변수가 아닌 것들은 CRT Startup이 호출하는 초기화 함수가 각 변수들의 초기화를 수행하게 된다. 반대로 정적 지역 변수의 경우 변수가 선언된 함수가 처음 호출되는 시점에 초기화가 수행될 것이다. 초기화 순서를 제대로 이해한다면 <리스트 5>에서 발생하는 문제를 해결할 수 있다.

<리스트 5> 전역 변수의 초기화 순서////////////////////////////////////////
// A.cpp
extern int g_B;
int InitializeA()
{
return g_B + 1; // ②
}int g_A = InitializeA(); // ①////////////////////////////////////////
// B.cpp
int InitializeB()
{
return 1; // ④
}int g_B = InitializeB(); // ③////////////////////////////////////////
// Main.cpp
extern int g_A;
void main()
{
cout << g_A << endl; // ⓐ
}


<리스트5>는 세 개의 cpp 파일(A.cpp, B.cpp, Main.cpp)로 이뤄져 있다. 목표는 ⓐ처럼 전역 변수 g_A를 출력하는 것이다. 예제의 결과를 예측해보자. 기대 결과는 2가 출력되는 것인데, 보통 이런 식의 순서를 가정하고 프로그래밍을 하는 개발자를 의외로 많이 볼 수 있다. main은 g_A를 출력하는데, g_A는 g_B에 1을 더한 값이고, g_B는 1로 초기화되기 때문이다. 실제로 이 예제를 실행할 경우 정상적으로 2가 출력될 수도 있지만 반대로 1이 출력될 수도 있다. 즉, 항상 2가 나오는지는 않는다는 것이다. 기대한 값 2가 정상적으로 나오는 경우는 오직 g_B가 g_A보다 먼저 초기화 되는 때다. 반대로 g_A가 g_B보다 먼저 초기화 된다고 가정해보자. g_B는 프로세스가 시작되는 순간 기본값인 0으로 설정돼 있는 상태다. g_B가 ③처럼 함수 초기화가 이루어지기 전에 ①과 같이 InitializeA가 먼저 호출되면 g_B에 1을 더한 값은 1이 될 것이다. 즉, g_A가 기대한 것과 다르게 1이 될 수도 있다.


CRT Startup은 전역 변수 g_A, g_B를 초기화하기 위해 각각 함수 InitializeA, InitializeB를 호출할 것이다. 문제는 두 함수 중에서 어느 것이 먼저 호출되는 지는 알 수 없다는 것이다. 보통 초기화 함수가 호출되는 순서는 오브젝트 단위로 진행되는데, A.cpp와 B.cpp 중 어느 것이 먼저 진행될 지는 딱히 정해진 바가 없다. 즉, 어떤 것이 먼저 호출될 것이라고 가정하고 프로그래밍을 해서는 절대로 안 된다는 것이다. 그렇다면 이런 문제를 해결하는 방법은 무엇일까 바로 정적 지역 변수의 초기화 원리를 이용하는 것이다.

<리스트 6> 초기화 제어////////////////////////////////////////
// A.cpp
int& GetB();
int InitializeA()
{
return GetB() + 1; // ②
}int g_A = InitializeA(); // ①////////////////////////////////////////
// B.cpp
int InitializeB()
{
return 1; // ④
}int& GetB() // ⓐ
{
static int s_B = InitializeB(); // ③
return s_B;
}////////////////////////////////////////
// Main.cpp
extern int g_A;
void main()
{
cout << g_A << endl;
}


<리스트 6>은 <리스트 5>를 정적 지역 변수를 사용해 변경한 것이다. 기대한 대로 초기화가 순서대로 이뤄지는 코드를 보여준다. 초기화 순서 제어의 목표는 g_A보다 g_B가 항상 먼저 초기화되는 것이다. 방법은 전역 변수 g_B 대신에 정적 지역 변수 s_B를 사용하는 것이다. 더 정확히 얘기하면 g_B 대신에 정적 지역 변수 s_B를 반환하는 함수인 GetB를 대신 사용한다.


이제 실행 흐름을 따라가 보자! g_A가 초기화되기 위해 ①의 InitializeA가 실행된다. InitializeA에서는 기존의 전역 변수 g_B 대신 함수 GetB를 사용한다. ⓐ의 함수 GetB를 살펴보자. 반환 타입이 int&이다. 본체에서는 정적 지역 변수 s_B를 생성하고 초기화한다. GetB가 최초 호출되는 시점에 s_B는 InitializeB에 의해 1로 초기화된다. 결국 g_A가 초기화 되는 과정에서 정적 지역 변수 s_B를 초기화하기 때문에 항상 s_B가 먼저 초기화 될 수밖에 없다. 그래서 항상 원하는 결과인 2가 출력될 수 있는 것이다. 더 간단히 설명하면 다음과 같다. s_B는 오직 함수 GetB가 처음 호출될 때 초기화가 되는데, GetB가 가장 처음에 호출되는 순간은 g_A를 초기화하기 위해서 함수 InitializeA를 호출할 때이다. 즉, s_B는 g_A보다 먼저 초기화되는 것이 보장되는 것이다.


정적 지역 변수를 이용한 초기화 제어는 의외로 많은 곳에서 사용된다. 대표적으로 싱글톤(Singleton)을 제작할 때라고 말할 수 있다. 간단하게 살펴보자!

<리스트 7> Singleton///////////////////////////////////////////////
class CSingletonA
{
private:
CSingletonA() {}
static CSingletonA inst; // ①
CSingletonA(const CSingletonA& other);public:
static CSingletonA& getInstance() { return inst; }
};CSingletonA CSingletonA::inst; // ②
///////////////////////////////////////////////
class CSingletonB
{
private:
CSingletonB() {}
CSingletonB(const CSingletonB& other);public:
static CSingletonB& getInstance()
{
static CSingletonB inst; // ③
return inst;
}
};


<리스트 7>은 싱글톤을 만드는 두 가지 방식을 보여준다. CSingletonA의 경우 싱글톤 객체를 ①처럼 정적 클래스 변수로 선언했다. 이럴 경우 ②와 같이 정적 클래스 변수의 정의가 필요하다. CSingletonA에는 문제가 있는데, 바로 다른 전역 변수를 초기화하기 위해 싱글톤을 사용할 경우 위험할 수 있다는 의미이다. 가령 다른 전역 변수의 초기화 함수에서 싱글톤을 얻기 위해 get Instance를 호출하는 순간 싱글톤 inst가 초기화가 되어있지 않을 수 있다. 그래서 이런 문제를 해결하기 위해 싱글톤을 만들 때는 CSingletonB와 같이 만든다. ③에서 볼 수 있듯이 싱글톤 객체 inst는 getInstance 함수의 정적 지역 변수이다. 따라서 다른 전역 변수의 초기화 함수에서 getInstance를 호출할 경우, 먼저 inst가 초기화될 수 있는 것이다.


정리

정적 지역 변수를 정리한다면 함수에서만 사용할 수 있는 전역 변수라고 할 수 있다. 동시에 가장 큰 특징이라고 한다면 전역 변수가 CRT Startup에 의해서 함수(생성자) 초기화가 이뤄지는 반면 정적 지역 변수는 변수 자신이 선언된 함수가 최초로 호출될 때 실질적인 초기화가 이뤄진다는 것이다. 이런 차이점을 정확히 이해한다면 순서 제어를 완벽하게 수행할 수 있으며, 그로 인해서 안전한 프로그래밍을 할 수 있다는 것이다.



출처 : 마이크로소프트웨어 12월호

제공 : 데이터전문가 지식포털 DBguide.net