C++에서는 static 키워드란 것이 있다. static의 의미는 정적이다는 것인데 이것이 변수에도 붙을 수도 있고, 함수에도 붙을 수 있다. 그리고 클래스 내에서 static은 또 다른 의미를 갖는다. static에 관한 내가 잊고 있었거나, 처음 알게된 사실들을 정리해보고자 한다.
우선 static변수의 특징부터 복습해보자. static변수는 전역 변수와 지역 변수로서의 특징을 모두 갖는다.
1. 전역 변수처럼 프로세스가 시작할 때(main 함수 시작 전에) 메모리가 할당되고 프로그램이 종료될 때 까지 메모리가 할당된 채로 남아있다. 지역 변수는 해당 블록이 끝나면 파괴되는 것과는 대비된다.
2. static 지역 변수가 블록 안에서 선언된 것이면 그 블록 밖에서는 사용이 불가능하다. 신기하게 메모리에는 프로그램이 끝날때 까지 남아있지만 사용은 못한다. 예를 들어 아래와 같이 강제적으로 블록을 만들어서 그 안에 static 지역 변수를 선언하면 밖에서 사용할 수 없다.
int main()
{
{
static int a = 3;
}
std::cout << a << std::endl; // error: 식별자 "a"가 정의되지 않았습니다
return 0;
}
3. 초기화는 한번만 되고, 그 다음부터는 초기화 코드가 무시된다. 예를 들어 아래와 같은 코드의 출력 결과는 3이 한번, 4가 4번 출력된다. 3으로 초기화하는 코드는 한번만 실행됐기 때문이다.
int main()
{
for(int i = 0; i < 5; ++i)
{
static int a = 3;
std::cout << a << std::endl;
a = 4;
}
}
4. static 변수는 힙, 스택, 데이터, 코드, bss(Block Started by Symbol) 영역중에 초기화가 됐으면 데이터 영역에, 초기화가 안된 static 변수는 bss 영역에 할당된다(전역 변수도 초기화 됐으면 데이터 영역, 안됐으면 bss영역에 할당).
5. 전역 static 변수는 선언된 파일 내에서만 참조를 허용한다.
클래스에서 멤버 변수와 멤버 함수도 static으로 선언 가능하다. 이때 static 멤버 변수의 특이한 점은 객체를 아무리 많이 생성해도 메모리 영역에 static 멤버 변수를 위한 공간이 하나만 생성된다. 그 말은 여러 객체가 static 멤버 변수를 공유한다는 의미이다.
class CStatic
{
public:
CStatic() { Num = 3; Num2 = 4; }
~CStatic() {}
static int Num;
int Num2;
int GetNum() { return this -> Num; }
};
int CStatic::Num;
int main()
{
CStatic cs1;
CStatic cs2;
CStatic cs3;
CStatic cs4;
CStatic cs5;
std::cout << &(cs1.Num) << std::endl;
std::cout << &(cs2.Num) << std::endl;
std::cout << &(cs3.Num) << std::endl;
std::cout << &(cs4.Num) << std::endl;
std::cout << &(cs5.Num) << std::endl;
}
또한 static 멤버 변수나 static 멤버 함수를 사용할 때는 범위 지정 연산자(::)로 사용할 수도 있다(물론 기존의 객체를 이용해서 사용하는 방법도 가능하다). 그리고 static 멤버 변수는 반드시 아래처럼 클래스 밖에 선언을 해줘야 한다.
class CStatic
{
public:
CStatic() { Num = 3; Num2 = 4; }
~CStatic() {}
static int Num;
int Num2;
int GetNum() { return this -> Num; }
};
int CStatic::Num; // 반드시 클래스밖에 선언 필요!
static 멤버 함수에는 중요한 점이 두가지 있는데 첫번째는 static 멤버 함수에서는 this키워드를 사용할 수 없다는 점이고, 두번째는 static 멤버 함수에서는 static 멤버 변수만 접근이 가능하다는 것이다.
해석하면 static 멤버 함수는 특정 object에 국한된 것이 아니기 때문에 this 포인터가 없다고 한다. 그리고 동일한 이유로 일반 멤버 함수에도 접근할 수 없다. 생각해보면 당연한게, this 포인터나 일반 멤버 변수는 객체마다 할당되는, 각 객체별로 따로 자신의 것을 가지고 있는 것이다. 그러니 당연히 이러한 특징을 가질 수 밖에 없다.
추가적으로 static 멤버 함수의 함수 포인터는 전역 함수 포인터와 형식이 똑같다. 일반 멤버 함수는 좀 더 복잡한 방식으로 함수 포인터를 사용할 수 있다.
class CStatic
{
public:
CStatic() {}
~CStatic() {}
static int Num;
static int StaticGetNum() { return Num; }
void GetNum() { std::cout << "Normal member function" << std::endl; }
};
int CStatic::Num = 10;
int main()
{
CStatic cs;
// static 멤버 함수 포인터는 전역 함수 포인터와 쓰는 방법이 같다
int (*pFunc)() = CStatic::StaticGetNum;
std::cout << pFunc() << std::endl;
// 일반 멤버 함수 포인터는 아래와 같이
// 리턴타입(클래스명:: *포인터이름)(파라미터) = &클래스명::함수이름
void (CStatic:: * pFunc2)() = &CStatic::GetNum;
// 호출은 static 멤버 함수가 아니니까 아래처럼 객체가 꼭 필요하다
(cs.*pFunc2)();
}
'공부 > C || C++' 카테고리의 다른 글
C++ 템플릿 특수화 (0) | 2021.07.24 |
---|---|
C++ 템플릿 클래스/함수 헤더파일에 선언과 정의 모두 해줘야 하는 이유 (0) | 2021.07.23 |
C++ 연산자 오버로딩(Operator overloading) (0) | 2021.07.22 |
함수 호출 규약(Calling convention) (0) | 2021.07.20 |
C++ iterator의 종류(input, output, forward, bidirectional, random access iterator) (0) | 2021.07.19 |