포털:컴퓨터공학/C++/static & const member variables

위키배움터

들어가기 전에[편집]

static member variables과 const member variables에 대해 잘 이해하기 위해서는 우선 프로그램이 메모리를 어떻게 사용하는지 알아야 합니다.

동적할당에 대해 배울때 살펴보았던 메모리 구조 그대로입니다. 다시한번 살펴봅시다.

스택
지역 변수가 쌓입니다. stack은 쌓아 올린 무언가를 뜻합니다.
프로그래머가 운영체제에 요청하여 사용할 수 있는 자유 공간입니다.
BSS(Block Started by Symbol)
정의만 이루어진, 전역변수와 static 변수가 들어갑니다.
BSS와 데이타 영역을 합쳐서 데이타 영역이라고 하기도 합니다.
데이타
정의와 초기화가 동시에 이루어진, 전역변수와 static변수가 들어갑니다.
BSS와 데이타 영역을 합쳐서 데이타 영역이라고 하기도 합니다.
코드
컴파일러에 의해 기계어(바이너리)로 만들어진 코드가 들어갑니다
우리가 쓴 소스가 들어간다고 생각하셔도 됩니다.

이 영역을 text영역이라고도 합니다. 글로 쓰여진 코드가 들어간다는 의미지요.


소스 코드가 여러가지 과정을 거쳐서 프로그램이 되었습니다. 이 프로그램이 시작되면 우선 CPU가 알아듣는 0과 1로 이루어져 있어는 코드가 메모리에 들어갑니다.

 T I P 
  여기서 메모리란 운영체제가 프로그램의 요청에 의해 프로그램에게 사용 권환을 준 가상적인 메모리 공간을 말합니다.
  이제 부터 메모리라는 말을 쓰면 모두 이 가상적인 메모리를 가리키는 것입니다.

코드 영역이 완성되면 이제 데이타 영역이 메모리에 자리 잡습니다. 그리곤 BSS가 잡고, 지금까지 메모리에 쌓인 영역에 잠금이 걸립니다. 잠금이 걸린다는 것은 지금까지 들어간 영역은 프로그램이 종료될 때까지 딱 그 크기 그대로 유지된다는 것입니다. 즉 이 잠금이 된 메모리 안에는 새로운 공간을 만들 수 없다는 것입니다. 공간 안에서의 변수 초기화와 호출은 자유롭게 할 수 있습니다.

힙 영역은 비어있는 커다란 공간으로서 자리 잡습니다. 힙 영역이 바로 동적할당을 하는 공간입니다.

스택 영역은 지역 변수들이 프로그램이 진행됨에 따라 생성되고 소멸되는 영역입니다. 변수가 선언될때 스택 영역 안에 변수 크기에 해당하는 공간이 설정되고, 보통 스코프(scope)가 닫힐때 그 공간이 사라집니다.

 T I P 
  보통의 경우 스코프는 중괄호로 묶인 구역을 뜻합니다.
int main(void)
{
 int a=10;
 {
  int b=20;
  for(int c=0; c<10; c++)
   cout<<c<<endl;
 }
 return 0;
}

  위 소스에서, 변수 a는 main함수의 중괄호가 닫힐 때(즉 main함수 스코프가 닫힐때) 사라집니다.
  변수 b는 return 0; 바로 위의 스코프가 닫힐 때 사라집니다.
  변수 c는 for문 스코프가 끝나면 사라집니다.

다음과 같은 표로 정리 할 수 있겠습니다.


영역 이름 들어가는 내용 변수 생성 타이밍 변수 파괴 타이밍
스택 지역변수 프로그램이 작동하는 동안, 지역 변수가 선언된 코드가 실행되는 순간 순간 스코프가 닫힐 때
동적 할당된 변수 프로그램이 작동하는 동안, 동적 할당 하는 순간 free나 delete로 해제시킬 때
BSS + 데이타 전역변수, static변수 코드를 읽은 직후, main()함수도 실행하기 전 프로그램 종료 시
코드 코드 모든 작동 전에(코드를 메모리에 쓰고 그것을 읽어서 실행함으로) 프로그램 종료 시
 주의하세요! 
  사실 코드 영역에 들어가는 것은 변수가 아니지요?

static member variable[편집]

학습자 여러분은 C에서 static 변수에 대해서 배웠을 것입니다. 기본을 철저하게 하기 위해서 다음 소스를 보면서 static에 대해서 복습해봅시다.

9.01 static 복습하기
#include <iostream>

using namespace std;

void CountFunction(void)
{static int count=0;
 cout<<count++<<endl;}


int main(void)
{
 CountFunction();
 CountFunction();
 CountFunction();

 return 0;
}
0
1
2

static이라는 단어를 제거하고 이 소스를 실행한다고 가정합시다. 우리는 0,0,0이라는 출력값을 받았을 것입니다. 하지만 static이라는 키워드를 사용함으로서 0,1,2라는 값을 받았습니다.

어떤 책에서는 이에 대해서 static 변수는 한번만 초기화되는 변수다라고 설명합니다.

다시 말해서 CountFunction()가 처음 호출될 때 countValue가 0으로 "딱 한번"초기화되고, 그 다음의 CountFunction()이 호출될 때는 countValue가 초기화되지 않는다는 것입니다(countValue가 다시 초기화 되지 않고 ++로 증가됨으로 0,1,2로 바뀐다는 식입니다).

하지만 우리는 '들어가기 전에'부분에서 지역변수가 스코프가 닫힐 때 제거된다고 배웠습니다.

따라서 방금의 설명은 잘못 된 것임을 알 수 있습니다. static 변수가 한번만 초기화 되는 변수라면, 첫번째 지역변수로 할당된 countValue는 0으로 초기화되었고, 그 다음부터 지역변수로 할당될 counValue는 초기화가 되지 않고 쓰레기값을 가지고 있을 것입니다.

static변수에 대한 제대로되고 충분한 설명은 다음과 같습니다. static변수는 메모리의 DATA 또는 BSS 영역에 들어갑니다.

처음 이 말을 들으면 이 설명이 어떤 의미를 지니는지 잘 이해가 가지 않을 것입니다.

DATA 또는 BSS 영역에 들어간다! 이 말을 주의깊게 생각해봅시다. DATA와 BSS영역에 변수는 언제 생성되었나요? 바로 프로그램 실행 직후이었죠. CountFunction()이 실행되기도 전에 생성되는 것입니다! 그리고 나서 DATA와 BSS영역에 '잠금'이 걸렸지요? 실행 과정을 차근차근 따라가봅시다. 다음은 9.01 소스와 똑같은 소스입니다.

9.02 static 이 소스를 분석하겠다!
#include <iostream>

using namespace std;

void CountFunction(void)
{static int count=0;
 cout<<count++<<endl;}


int main(void)
{
 CountFunction();
 CountFunction();
 CountFunction();

 return 0;
}
0
1
2

1.코드가 쌓입니다. 2.그리고 나서 DATA와 BBS 영역이 만들어질 것입니다. 이 영역엔 무엇이 들어가야 하나요? 바로 static변수인 count이지요.


3.이제 잠금이 걸리고, 힙과 스택 영역이 설정될 것입니다.

4.이제 코드 영역에 쌓인 것을 읽어 프로그램을 작동시킬 것입니다.

어떤 결과를 보이나요? count가 이미 데이타 영역에 존재하기 때문에 CountFunction함수에서 count를 만들지 않습니다. 따라서 CountFuntion함수 스코프가 끝날때 count변수가 제거되지도 않지요.

실상, static 변수는 '전역변수'라고 이해하는 것이 더 직관적입니다.

9.03 이렇게 이해하자!
#include <iostream>

using namespace std;

int count=0;

void CountFunction(void)
{//전역변수 count를 이 함수에서만 사용할 수 있다
 cout<<count++<<endl;}


int main(void)
{
 CountFunction();
 CountFunction();
 CountFunction();

 return 0;
}
0
1
2

즉, static변수를 '선언된 스코프 안에서만 사용할 수 있는 전역변수'로 이해하는 것입니다. 다음과 같이 정리해보겠습니다.

  1. 지역 변수는 스코프가 닫히면 제거됩니다.
  2. static변수는 BSS또는 데이타 영역에 저장됩니다.
  3. static변수는 선언된 스코프 안에서만 사용할 수 있는 전역변수입니다!

이제 클래스 안에서의 static멤버 변수를 더 쉽게 이해할 수 있으실 것입니다.

다음 소스를 보면서 이해해봅시다.

9.04 (잘못된 소스) 클래스 안에서의 static 멤버 변수
#include <iostream>

using namespace std;

class UsingStatic{
 static int count=0;
public:
 void countUp(void)
 {
  count++;
 }
 void view(void)
 {
  cout<<count<<endl;
 }
};


int main(void)
{
 UsingStatic a;
 return 0;
}

무엇이 문제일까요? static변수는 데이타 영역에 미리 할당되어야 합니다. 하지만 class UsingStatic이라는 것은 하나의 class type을 만든 것뿐입니다. class에 무엇이 들어있는지 "정의"한 것일뿐 실제로 변수를 생성하는 "선언"한 것이 아니라는 말입니다. 하지만 static변수는 프로그램이 돌아가기 전에 미리 존재해야 하지 않습니까? C++은 다음 소스처럼 사용하라고 합니다.

9.05 클래스 안에서의 static 멤버 변수
#include <iostream>

using namespace std;

class UsingStatic{
 static int count;
public:
 void countUp(void)
 {
  count++;
 }
 void view(void)
 {
  cout<<count<<endl;
 }
};

int UsingStatic::count=0;

int main(void)
{
 UsingStatic a;
 return 0;
}

count변수를 전역변수처럼 선언했습니다. 이 문장은 UsingStatic안에 있는 static int count를 실제로 존재하게 한 것입니다. 이제 count변수를 제대로 사용할 수 있습니다.

9.06 static 멤버 변수의 사용
#include <iostream>

using namespace std;

class UsingStatic{
 static int count;
public:
 void countUp(void)
 {
  count++;
 }
 void view(void)
 {
  cout<<count<<endl;
 }
};

int UsingStatic::count=0;

int main(void)
{
 UsingStatic a;
 UsingStatic b;
 UsingStatic c;
 
 
 a.view();

 return 0;
}
3

static 멤버변수에 대한 이해는 쉽지 않습니다. 메모리 구조와 락, 그리고 '정의'와 '선언'의 명확한 차이까지 알고 있어야 하기 때문입니다.

정리해보겠습니다.

  1. 메모리 구조 : 코드,데이타,BSS(잠금!), 힙,스택
  2. static변수는 데이타 또는 BSS영역에 들어간다
  3. static변수는 정의된 장소에서만 사용할 수 있는 전역변수와 같다.
  4. 클래스에서 static멤버 변수는, 따로 외부에 전역변수처럼 선언해주어야 한다.
  5. 그것은 클래스 안에서 static멤버 변수에 대해 써놓는 것이 실제로 변수를 존재하게 하는 '선언'이 아니라 무엇을 어떻게 쓴다는 '정의'를 의미하기 때문이다.

const member variable[편집]

const 멤버 변수는 static멤버 변수에 비해 단순합니다.

const 변수는 "다시 초기화할 수 없는 변수"입니다. 다음 예제를 보면서 이해해봅시다.

9.07 const 복습하기(오류가 있는 소스)
#include <iostream>

using namespace std;

int main(void)
{
 const int a = 10;
 cout<<a<<endl;
 
 a=20; // error

 cout<<a<<endl;

 return 0;
}

왜 에러가 발생했을까요? const는 단 한번만 초기화 가능하게만들기 때문이죠. a는 이미 10으로 초기화 되었었습니다. const가 붙여져 선언되었으니 20으로 다시 초기화할 수 없는 것이지요. 다음과 같이 부분을 주석처리하면 정상적으로 실행됩니다.

9.08 const 복습하기(정상 작동하는 소스!)
#include <iostream>

using namespace std;

int main(void)
{
 const int a = 10;
 cout<<a<<endl;
 
 //a=20;

 cout<<a<<endl;

 return 0;
}
 10
 10

이 const를 멤버 변수에 적용시켜 보도록합시다. 9.09 소스가 제대로 작동할지 생각해봅시다.

9.09 const 멤버 변수, 어떻게 쓰지(오류가 있는 소스)
#include <iostream>

using namespace std;

class usingConst
{
 const int value;
public:
 usingConst(int input)
 {
  value = input;
 }
 
 void printValue(void)
 {
  cout<<value;
 }
};

int main(void)
{
 usingConst A(10); 

 return 0;
}

왜 오류가 날까요? 예상하기 어렵습니다. 그냥 C++의 문법이 그렇게 정하고 있기 때문에 오류가 나는 것이거든요. 그럼 클래스의 멤버 변수를 const처리하는 것은 불가능할까요? 아닙니다. 다음과 같은 방법이 있습니다.

9.10 const 멤버 변수, 이렇게 쓰자!
#include <iostream>

using namespace std;

class usingConst
{
 const int value; 
public:
 usingConst(int input) : value(input)
 {
  //value = input;
 }
 
 void printValue(void)
 {
  cout<<value;
 }
};

int main(void)
{
 usingConst A(10); 

 return 0;
}
 10

간단합니다. 멤버 변수의 이런 모습의 초기화는 굳이 const화 되지 않아도 적용할 수 있습니다.

9.11 멤버 변수의 이런 모습 초기화
#include <iostream>

using namespace std;

class likeThis
{
 char * name;
 int age;
public:
 likeThis(char * inputName, int inputAge) : name(new char[strlen(inputName)+1]), age(inputAge)
 {
  strcpy(name, inputName);
 }
 
 void printValue(void)
 {
  cout<<name<<endl<<age;
 }
};

int main(void)
{
 likeThis one("Pak-JaeHyeon",21);
 one.PrintValue();
 return 0;
}
 Par_JaeHyeon
 21

정리[편집]

[static & const member variables]에서 다음 내용을 배웠습니다. 복습은 최고의 공부법!

  1. 코드, 데이타, BSS, 힙, 스택
  2. 지역변수는 어디에 들어가지? 동적할당된 변수는? static변수는? 전역 변수는?
  3. static 변수가 어디에 들어가는지 다시 한번 말해보아라.
  4. 클래스 내부에서 static 변수를 사용할때 왜 외부에 static변수를 '선언'해야하나? 이것과 같이 생각해봐라. 클래스 정의시 쓴 static 변수는 '선언'이 아니라 '정의'다!
  5. 변수를 단 한번만 초기화도록 하는 키워드는?
  6. const 멤버 변수를 초기화하는 방법은?