Storage Duration
기억 존속 시간
- 메모리에 데이터를 저장하고 있는 시간을 구간별로 나누어 놓은 개념이다.
- 데이터의 Lifespan(수명)이라 할 수 있다.
* 다섯 가지 기억 공간에 대한 요약
Storage (기억 공간) |
Duration (기억 존속 시간) |
Scope (사용 범위) |
Linkage (링크) |
How Declared (선언 방법) |
Automatic | 자동 | 블록 | X | 블록 안에 선언 |
Register | 자동 | 블록 | X | 블록 안에 \(\texttt{register}\) 키워드를 통해 선언 |
Static with No Linkage |
정적 | 블록 | X | 블록 안에 \(\texttt{static}\) 키워드를 통해 선언 |
Static with Internal Linkage |
정적 | 파일 | 내부 | 함수 바깥에 \(\texttt{static}\) 키워드를 통해 선언 |
Static with External Linkage |
정적 | 파일 | 외부 | 함수 바깥에 선언 |
1. Automatic Storage Duration (자동 기억 존속 시간)
- Program Execution(프로그램 실행 흐름)이 이들을 정의하고 있는 함수, 블럭 안으로 들어갈 때(실행될 때) 메모리가 대입되고, 해당 함수나 블록을 떠날 때 해제된다.
- 함수 매개변수나 함수 정의 내에 선언된 변수와 같은, Automatic Variable(자동변수)들이 이에 해당된다.
- 자동 변수들은 자신을 포함하고 있는 블록 또는 함수 안에서만 유효하다.
- 자동 변수는 Stack(스택)에 저장되며, 스택은 프로그램 실행시간 동안 메모리 크기가 유동적으로 증감된다.
2. Static Storage Duration (정적 기억 존속 시간)
- 프로그램이 실행되는 전체 시간 동안 존속되는 영역이다.
- 함수 외부의 전역 변수, \(\texttt{static}\) 키워드로 정의된 변수와 같은 데이터들이 이에 해당된다.
- 정의되는 모든 함수는 기본적으로 정적 기억 존속 시간내에 포함되며, 외부 링크를 가진다.
- C++에서는 링크의 종류에 따라 3가지 타입의 정적 변수를 가진다. (External Linkage, Internal Linkage, No Linkage)
3. Thread Storage Duration (쓰레드 존속 시간, 참조)
- C++11에서 추가된 개념이다.
- \(\texttt{_Thread_local}\) Storage-Class Specifier를 통해 구현할 수 있다.
- 해당 쓰레드가 존속되는 전체 실행시간동안 유지되는 시간 영역이다.
4. Dynamic Storage Duration (동적 기억 존속 시간)
- Free Store(자유 공간) 혹은 Heap(힙)이 해당되는 영역이다.
- Heap이라는 Memory Pool(메모리 풀) 내에 선언된 데이터의 수명은 프로그램 실행시간이나 함수의 수명에 얽매이지 않고, 프로그램이 종료되기 전까지에 한해서 프로그래머의 임의대로 조정 가능하다. (사용자가 메모리 제어권한을 가진다.)
- 동적으로 할당받은 메모리를 해제하지 않을 경우, Memory Leak(메모리 누수)*가 발생할 수 있어, 프로그램이 종료될 때 같이 반환될 것이라는 수동적인 생각보다는 \(\texttt{delete}\)를 통한 적극적인 메모리 관리가 필요하다.
- C++의 \(\texttt{new}\) 와 \(\texttt{delete}\) 키워드에 의해 관리되는 영역이다.
* Memory Leak (메모리 누수) : 동적으로 할당받은 메모리를 해제하지 않아 해당 메모리에 접근할 방도가 없어지는 현상으로, 이를 방지하는 프로그래밍적 방법으로는 \(\texttt{new}\) 연산자와 \(\texttt{delete}\) 연산자를 가능한 한 가깝게 위치시키는 것이 있다.
Automatic Variable (자동 변수)
- 함수 매개변수나 함수 안에서 선언된 변수와 같이 자동 기억 존속 시간을 가지는 변수 혹은 이름들을 의미한다.
- Local Scope(지역 사용 범위)를 가지며, No Linkage(링크 없음) 형태이다.
- 자동변수의 메모리 할당 시점은 함수 혹은 블록이 시작될 때이며, 자동변수의 사용 범위는 선언된 시점부터 함수 혹은 블록이 종료될 시점까지이다. (무조건 블록이 시작될 때 부터 사용가능한 것이 아니다.)
- C++ 컴파일러는 메모리의 일부를 예약해 두고, 변수(이름)들의 생성, 소멸을 Stack으로 관리하는 것이 일반적이다.
- 이 때, Stack의 LIFO(Last In-First Out; 후입선출)적 성질은 매개변수의 전달 과정을 간단하게 만든다.
* Register Variable (레지스터 변수)
- C에서의 \(\texttt{register}\) 키워드는 컴파일러가 CPU 레지스터에 자동 변수를 저장하게 하는 기능으로 제공된 개념이었다.
- C++11 이전 버전에서는 H/W와 컴파일러가 매우 정밀하게 개발된 경우를 제외하고는 레지스터 변수는 "자주 사용되는 변수" 혹은 "특별한 조치가 필요한 변수"의 의미로 사용되었다.
- C++11 이후 버전에서의 \(\texttt{register}\) 키워드는 한 변수가 자동 변수임을 명시하는데 사용되고 있으며, 오로지 자동 변수들만 사용중인 경우에서의 \(\texttt{register}\) 키워드는 사용자가 외부 변수와 동일한 이름을 지닌 한 자동 변수를 구분짓는 의미로 사용되기도 한다.
- 이를 대체하기 위해 \(\texttt{auto}\) 키워드를 사용할 수 있지만, \(\texttt{register}\) 키워드를 이용중인 기존의 코드가 \(\texttt{auto}\) 키워드를 인식하지 못하는 경우에 대비하기 위해 C++11이후 버전에도 \(\texttt{register}\) 키워드가 계승되었다.
* Classic K&R C 에서는 자동 배열과 자동 구조체의 초기화를 허용하지 않고, 정적 배열과 정적 구조체의 초기화는 허용한다.
* ANSI C와 C++에서는 두 가지 경우를 모두 초기화할 수 있다.
Static Variable (정적 변수)
- C와 C++에서는 세 가지 유형의 정적 변수를 정의할 수 있다. (External Linkage, Internal Linkage, No Linkage)
- 세 유형의 변수 모두 프로그램이 실행되는 전체 시간동안 존속된다.
- 정적 변수의 개수는 프로그램이 실행되는 동안 변함없기 때문에, 자동 변수와 같이 Stack에 관리하는 등의 특별한 장치가 필요하지 않다.
- 초기화되지 않은 모든 정적 변수들은 모든 비트가 0으로 세팅되는 Zero-Initialized(제로 초기화)가 수행된다.
(정적 변수는 명시적인 초기화 작업이 없을 경우, 기본적으로 0으로 초기화된다.)
1. External Linkage Static Duration (외부 링크를 가지는 정적 존속 시간)
- 일반적으로, "External Variables"(외부 변수)라 부르는 변수가 이에 속한다.
- 어떤 블록에도 속하지 않는 바깥에 이름을 선언해서 정의할 수 있다.
- 프로그램을 구성하는 모든 파일들에서 프로그램 종료 시까지 이름을 사용할 수 있다.
* The One Definition Rule (ODR; 단일 정의 원칙) 과 \(\texttt{extern}\) 키워드
- C++에서는 하나의 변수에 대하여 오직 한 번의 정의만을 허용한다.
- 그러나, 외부 변수의 경우 사용되어지는 모든 각각의 파일에서 선언되어져야 하는데, 이러한 점이 ODR에 위배되므로 C++에서는 해결방안으로 \(\texttt{extern}\) 키워드를 제공한다.
+ 인라인 함수는 이 규칙을 따르지 않는다. 따라서, 헤더 파일에 인라인 함수 정의들을 넣고 각각의 파일에서 여러번 포함시켜도 문제가 발생하지 않는다.
- C++에서는 저장소의 생성 여부를 이용해 변수 선언 방식을 두 종류로 나눈다.
1. 일반적인 선언을 정의하는 것으로 이 경우에는 저장소가 생성된다.
2. 참조 선언과 같이 기존의 변수를 이용하는 선언은 저장소를 생성하지 않는다.
(여기서, 참조 선언은 \(\texttt{extern}\) 키워드를 사용하며 초기화하지 않는 선언을 의미한다.)
- 하나의 파일에서 외부 변수가 정의 및 초기화되고, 그것을 이용하는 다른 파일에서는 \(\texttt{extern}\) 키워드를 이용하여 초기화하지 않고 참조 선언해주어야 한다.
// File_01.cpp
extern int cats = 20; // 초기화 되므로 정의에 해당된다. (extern 키워드가 없어도 똑같은 동작을 수행할 것이다.)
int dogs = 22; // 정의에 해당된다.
int fleas; // 정의에 해당된다.
// File_02.cpp
extern int cats; // 참조 선언에 해당된다.
extern int dogs; // 참조 선언에 해당된다.
// 이 파일의 경우, 외부 변수 fleas는 사용할 수 없다!
// File_98.cpp
extern int cats; // 참조 선언에 해당된다.
extern int dogs; // 참조 선언에 해당된다.
extern int fleas; // 참조 선언에 해당된다. (fleas를 사용할 수 있다!)
- 만약, \(\texttt{extern}\) 키워드를 이용하여 선언 및 초기화를 진행했다면 이는 외부 변수의 정의(초기화를 한 이상 참조 선언이 아니라, 명시적 외부 변수의 정의에 해당!)에 해당된다. 이런 경우에 \(\texttt{extern}\) 키워드는 정의 과정에서 필수적이지 않으며, 다른 파일에서 사용되는 외부 변수임을 명시적으로 표현하는 방법 중 하나가 된다.
* 함수의 기억 존속 시간
- 정의되는 모든 함수는 기본적으로 외부 링크를 갖는 정적 기억 존속 시간을 가지며, 함수 원형에 \(\texttt{extern}\) 키워드를 적용하여 다른 파일에 정의되어 있음을 명시적으로 표현할 수 있으나, 이는 선택사항이다.
- 프로그램이 어떤 파일에 포함되어 있는 함수를 찾는다면 이는 두 가지 중 하나에 해당되는데, 그 파일은 프로그램의 일부로서 컴파일되고 있는 파일 중 하나이거나, 링커에 의해 탐색되는 라이브러리 파일 중 하나이다.
- 함수 원형과 정의 부분에 \(\texttt{static}\) 키워드를 이용하여 함수에 내부 링크를 부여할 수 있으며, 해당 함수의 사용 범위를 단일 파일 범위으로 제한할 수 있다.
- 변수와 마찬가지로, 동일한 이름의 외부 함수명과 정적 함수명이 공존할 경우, 정적 함수명이 외부 함수명을 "Hides"하게 된다.
Ex. 내부 링크를 갖는 함수 원형/정의 선언 예시
static int Func (double x); // 원형과 정의에 모두 static 키워드를 사용해야 한다!
...
static int Func (double x) {
...
}
2. Internal Linkage Static Duration (내부 링크를 가지는 정적 존속 시간)
- 어떠한 블록에도 속하지 않는 바깥에 \(\texttt{static}\) 키워드를 이용해 선언해서 정의할 수 있다.
- 해당 파일 내에서 프로그램 종료 시까지 이름을 사용할 수 있다. (정적 저장 기간 + 파일 범위)
- 하나의 파일 내에서만 사용되는 변수이므로, 다른 파일 변수들과의 충돌을 걱정할 필요가 없다.
(그러므로, 한 파일 내에서만 사용할 용도의 전역 변수라면, \(\texttt{static}\) 키워드를 꼭 사용하자!)
3. No Linkage Static Duration (링크를 가지지 않는 정적 존속 시간)
- 특정 블록 안에서 \(\texttt{static}\) 키워드를 이용해 선언해서 정의할 수 있다.
- 정적 지역 변수는 프로그램이 시작될 때 한 번만 초기화되고, 그 이후에 루프나 재귀를 통해 나타나는 정적 지역 변수 초기화 구문은 무시된다. (이러한 특징은, 누계를 계산할 때 굉장히 유용하게 사용된다.)
Ex. 정적 지역 변수를 이용한 누계 계산 예시
void strCount (const char* str) {
using namespace std;
static int total = 0; // 정적 지역 변수 total은 0으로 초기화 된 이후, 다시 초기화되지 않는다.
// strCount() 함수가 여러 번 호출되어도, 위 초기화 구문은 프로그램 실행 시에, 단 한 번만 수행되고
// 그 이후에는 무시되는 구문이다.
int count = 0;
cout << "\"" << str << "\"에는 ";
while (*str++)
count++;
total += count;
cout << count << "개의 문자가 있다." << endl;
cout << "지금까지 총 " << total << " 개의 문자가 입력되었다." << endl;
return;
}
...
// total이 초기화 구문에 의해 0으로 초기화 된 후,
strCount("Hello"); // total에 5가 누산되고, 초기화 구문은 실행되지 않는다.
strCount("What`s up"); // total에 14가 누산되고(5+9), 초기화 구문은 실행되지 않는다.
* 블록 외부에서의 \(\texttt{static}\) 키워드 유무는 외부/내부 링크를 결정하고, 블록 내부에서의 \(\texttt{static}\) 키워드 유무는 기억 존속 시간을 결정하는 모습에서 Keyword Overloading(키워드 오버로딩)이라는 용어가 탄생했다.
* 정적 변수 초기화
- 정적 변수는 Zero-Initialization(제로 초기화), Constant-Expression Initialization(상수 표현 초기화), Dynamic Initialization(동적 초기화)가 가능하다.
- 정적 초기화는 제로 초기화와 상수 표현 초기화를 아우르는 용어이며, 컴파일러가 파일(Translation Unit)을 처리할 때 변수가 초기화된다는 의미이다.
- 모든 정적 변수는 우선적으로 제로 초기화가 시행된다. (따로 명시된 초기화 과정이 있다 하더라도)
- 동적 초기화는 해당 변수가 이후에 초기화됨을 의미한다.
#include <cmath>
int x; // 제로 초기화
int z = 13 * 13; // 제로 초기화 -> 상수 표시 초기화
const double pi = 3.0 * atan(1.0); // 제로 초기화 -> 동적 초기화
- C++11에서 새로 추가된 \(\texttt{constexpr}\) 키워드를 통해 상수 표기를 조금 더 다양화 시킬 수 있다.
* Scope Resolution Operator \(\texttt{::}\) (사용 범위 결정 연산자 \(\texttt{::}\))
- 지역 변수명이 전역 변수명과 겹칠 경우에, 지역 변수가 전역 변수를 Hide 하게 되는데, 이런 상황에서 전역 변수를 사용하고자 할 때, 전역 변수명 앞에 \(\texttt{::}\) 연산자를 붙이면 전역 변수를 사용할 수 있다.
Ex. 사용 범위 결정 연산자 사용 예시
int Global = 135;
int main() {
...
int Global = 246;
std::cout << ::Global; // 이 경우에, 135가 출력된다!
...
}
- 전역 변수는 접근이 쉬운편에 속하는데, 접근이 쉬운 것은 프로그램의 신뢰성을 떨어뜨리며, 이러한 불필요한 접근을 잘 막으면 막을수록 데이터의 Integrity(무결성)가 보전되는 양상을 보인다. 데이터를 무차별적으로 전역 변수로 선언하기 보다는, 지역 변수로 만들고 필요한 함수에만 전달할 수 있게 허용하는 것이 좋다. 전역 변수는 상수 데이터 정도를 나타내는 데 유용하다.
Storage Duration
기억 존속 시간
- 메모리에 데이터를 저장하고 있는 시간을 구간별로 나누어 놓은 개념이다.
- 데이터의 Lifespan(수명)이라 할 수 있다.
* 다섯 가지 기억 공간에 대한 요약
Storage (기억 공간) |
Duration (기억 존속 시간) |
Scope (사용 범위) |
Linkage (링크) |
How Declared (선언 방법) |
Automatic | 자동 | 블록 | X | 블록 안에 선언 |
Register | 자동 | 블록 | X | 블록 안에 \(\texttt{register}\) 키워드를 통해 선언 |
Static with No Linkage |
정적 | 블록 | X | 블록 안에 \(\texttt{static}\) 키워드를 통해 선언 |
Static with Internal Linkage |
정적 | 파일 | 내부 | 함수 바깥에 \(\texttt{static}\) 키워드를 통해 선언 |
Static with External Linkage |
정적 | 파일 | 외부 | 함수 바깥에 선언 |
1. Automatic Storage Duration (자동 기억 존속 시간)
- Program Execution(프로그램 실행 흐름)이 이들을 정의하고 있는 함수, 블럭 안으로 들어갈 때(실행될 때) 메모리가 대입되고, 해당 함수나 블록을 떠날 때 해제된다.
- 함수 매개변수나 함수 정의 내에 선언된 변수와 같은, Automatic Variable(자동변수)들이 이에 해당된다.
- 자동 변수들은 자신을 포함하고 있는 블록 또는 함수 안에서만 유효하다.
- 자동 변수는 Stack(스택)에 저장되며, 스택은 프로그램 실행시간 동안 메모리 크기가 유동적으로 증감된다.
2. Static Storage Duration (정적 기억 존속 시간)
- 프로그램이 실행되는 전체 시간 동안 존속되는 영역이다.
- 함수 외부의 전역 변수, \(\texttt{static}\) 키워드로 정의된 변수와 같은 데이터들이 이에 해당된다.
- 정의되는 모든 함수는 기본적으로 정적 기억 존속 시간내에 포함되며, 외부 링크를 가진다.
- C++에서는 링크의 종류에 따라 3가지 타입의 정적 변수를 가진다. (External Linkage, Internal Linkage, No Linkage)
3. Thread Storage Duration (쓰레드 존속 시간, 참조)
- C++11에서 추가된 개념이다.
- \(\texttt{_Thread_local}\) Storage-Class Specifier를 통해 구현할 수 있다.
- 해당 쓰레드가 존속되는 전체 실행시간동안 유지되는 시간 영역이다.
4. Dynamic Storage Duration (동적 기억 존속 시간)
- Free Store(자유 공간) 혹은 Heap(힙)이 해당되는 영역이다.
- Heap이라는 Memory Pool(메모리 풀) 내에 선언된 데이터의 수명은 프로그램 실행시간이나 함수의 수명에 얽매이지 않고, 프로그램이 종료되기 전까지에 한해서 프로그래머의 임의대로 조정 가능하다. (사용자가 메모리 제어권한을 가진다.)
- 동적으로 할당받은 메모리를 해제하지 않을 경우, Memory Leak(메모리 누수)*가 발생할 수 있어, 프로그램이 종료될 때 같이 반환될 것이라는 수동적인 생각보다는 \(\texttt{delete}\)를 통한 적극적인 메모리 관리가 필요하다.
- C++의 \(\texttt{new}\) 와 \(\texttt{delete}\) 키워드에 의해 관리되는 영역이다.
* Memory Leak (메모리 누수) : 동적으로 할당받은 메모리를 해제하지 않아 해당 메모리에 접근할 방도가 없어지는 현상으로, 이를 방지하는 프로그래밍적 방법으로는 \(\texttt{new}\) 연산자와 \(\texttt{delete}\) 연산자를 가능한 한 가깝게 위치시키는 것이 있다.
Automatic Variable (자동 변수)
- 함수 매개변수나 함수 안에서 선언된 변수와 같이 자동 기억 존속 시간을 가지는 변수 혹은 이름들을 의미한다.
- Local Scope(지역 사용 범위)를 가지며, No Linkage(링크 없음) 형태이다.
- 자동변수의 메모리 할당 시점은 함수 혹은 블록이 시작될 때이며, 자동변수의 사용 범위는 선언된 시점부터 함수 혹은 블록이 종료될 시점까지이다. (무조건 블록이 시작될 때 부터 사용가능한 것이 아니다.)
- C++ 컴파일러는 메모리의 일부를 예약해 두고, 변수(이름)들의 생성, 소멸을 Stack으로 관리하는 것이 일반적이다.
- 이 때, Stack의 LIFO(Last In-First Out; 후입선출)적 성질은 매개변수의 전달 과정을 간단하게 만든다.
* Register Variable (레지스터 변수)
- C에서의 \(\texttt{register}\) 키워드는 컴파일러가 CPU 레지스터에 자동 변수를 저장하게 하는 기능으로 제공된 개념이었다.
- C++11 이전 버전에서는 H/W와 컴파일러가 매우 정밀하게 개발된 경우를 제외하고는 레지스터 변수는 "자주 사용되는 변수" 혹은 "특별한 조치가 필요한 변수"의 의미로 사용되었다.
- C++11 이후 버전에서의 \(\texttt{register}\) 키워드는 한 변수가 자동 변수임을 명시하는데 사용되고 있으며, 오로지 자동 변수들만 사용중인 경우에서의 \(\texttt{register}\) 키워드는 사용자가 외부 변수와 동일한 이름을 지닌 한 자동 변수를 구분짓는 의미로 사용되기도 한다.
- 이를 대체하기 위해 \(\texttt{auto}\) 키워드를 사용할 수 있지만, \(\texttt{register}\) 키워드를 이용중인 기존의 코드가 \(\texttt{auto}\) 키워드를 인식하지 못하는 경우에 대비하기 위해 C++11이후 버전에도 \(\texttt{register}\) 키워드가 계승되었다.
* Classic K&R C 에서는 자동 배열과 자동 구조체의 초기화를 허용하지 않고, 정적 배열과 정적 구조체의 초기화는 허용한다.
* ANSI C와 C++에서는 두 가지 경우를 모두 초기화할 수 있다.
Static Variable (정적 변수)
- C와 C++에서는 세 가지 유형의 정적 변수를 정의할 수 있다. (External Linkage, Internal Linkage, No Linkage)
- 세 유형의 변수 모두 프로그램이 실행되는 전체 시간동안 존속된다.
- 정적 변수의 개수는 프로그램이 실행되는 동안 변함없기 때문에, 자동 변수와 같이 Stack에 관리하는 등의 특별한 장치가 필요하지 않다.
- 초기화되지 않은 모든 정적 변수들은 모든 비트가 0으로 세팅되는 Zero-Initialized(제로 초기화)가 수행된다.
(정적 변수는 명시적인 초기화 작업이 없을 경우, 기본적으로 0으로 초기화된다.)
1. External Linkage Static Duration (외부 링크를 가지는 정적 존속 시간)
- 일반적으로, "External Variables"(외부 변수)라 부르는 변수가 이에 속한다.
- 어떤 블록에도 속하지 않는 바깥에 이름을 선언해서 정의할 수 있다.
- 프로그램을 구성하는 모든 파일들에서 프로그램 종료 시까지 이름을 사용할 수 있다.
* The One Definition Rule (ODR; 단일 정의 원칙) 과 \(\texttt{extern}\) 키워드
- C++에서는 하나의 변수에 대하여 오직 한 번의 정의만을 허용한다.
- 그러나, 외부 변수의 경우 사용되어지는 모든 각각의 파일에서 선언되어져야 하는데, 이러한 점이 ODR에 위배되므로 C++에서는 해결방안으로 \(\texttt{extern}\) 키워드를 제공한다.
+ 인라인 함수는 이 규칙을 따르지 않는다. 따라서, 헤더 파일에 인라인 함수 정의들을 넣고 각각의 파일에서 여러번 포함시켜도 문제가 발생하지 않는다.
- C++에서는 저장소의 생성 여부를 이용해 변수 선언 방식을 두 종류로 나눈다.
1. 일반적인 선언을 정의하는 것으로 이 경우에는 저장소가 생성된다.
2. 참조 선언과 같이 기존의 변수를 이용하는 선언은 저장소를 생성하지 않는다.
(여기서, 참조 선언은 \(\texttt{extern}\) 키워드를 사용하며 초기화하지 않는 선언을 의미한다.)
- 하나의 파일에서 외부 변수가 정의 및 초기화되고, 그것을 이용하는 다른 파일에서는 \(\texttt{extern}\) 키워드를 이용하여 초기화하지 않고 참조 선언해주어야 한다.
// File_01.cpp
extern int cats = 20; // 초기화 되므로 정의에 해당된다. (extern 키워드가 없어도 똑같은 동작을 수행할 것이다.)
int dogs = 22; // 정의에 해당된다.
int fleas; // 정의에 해당된다.
// File_02.cpp
extern int cats; // 참조 선언에 해당된다.
extern int dogs; // 참조 선언에 해당된다.
// 이 파일의 경우, 외부 변수 fleas는 사용할 수 없다!
// File_98.cpp
extern int cats; // 참조 선언에 해당된다.
extern int dogs; // 참조 선언에 해당된다.
extern int fleas; // 참조 선언에 해당된다. (fleas를 사용할 수 있다!)
- 만약, \(\texttt{extern}\) 키워드를 이용하여 선언 및 초기화를 진행했다면 이는 외부 변수의 정의(초기화를 한 이상 참조 선언이 아니라, 명시적 외부 변수의 정의에 해당!)에 해당된다. 이런 경우에 \(\texttt{extern}\) 키워드는 정의 과정에서 필수적이지 않으며, 다른 파일에서 사용되는 외부 변수임을 명시적으로 표현하는 방법 중 하나가 된다.
* 함수의 기억 존속 시간
- 정의되는 모든 함수는 기본적으로 외부 링크를 갖는 정적 기억 존속 시간을 가지며, 함수 원형에 \(\texttt{extern}\) 키워드를 적용하여 다른 파일에 정의되어 있음을 명시적으로 표현할 수 있으나, 이는 선택사항이다.
- 프로그램이 어떤 파일에 포함되어 있는 함수를 찾는다면 이는 두 가지 중 하나에 해당되는데, 그 파일은 프로그램의 일부로서 컴파일되고 있는 파일 중 하나이거나, 링커에 의해 탐색되는 라이브러리 파일 중 하나이다.
- 함수 원형과 정의 부분에 \(\texttt{static}\) 키워드를 이용하여 함수에 내부 링크를 부여할 수 있으며, 해당 함수의 사용 범위를 단일 파일 범위으로 제한할 수 있다.
- 변수와 마찬가지로, 동일한 이름의 외부 함수명과 정적 함수명이 공존할 경우, 정적 함수명이 외부 함수명을 "Hides"하게 된다.
Ex. 내부 링크를 갖는 함수 원형/정의 선언 예시
static int Func (double x); // 원형과 정의에 모두 static 키워드를 사용해야 한다!
...
static int Func (double x) {
...
}
2. Internal Linkage Static Duration (내부 링크를 가지는 정적 존속 시간)
- 어떠한 블록에도 속하지 않는 바깥에 \(\texttt{static}\) 키워드를 이용해 선언해서 정의할 수 있다.
- 해당 파일 내에서 프로그램 종료 시까지 이름을 사용할 수 있다. (정적 저장 기간 + 파일 범위)
- 하나의 파일 내에서만 사용되는 변수이므로, 다른 파일 변수들과의 충돌을 걱정할 필요가 없다.
(그러므로, 한 파일 내에서만 사용할 용도의 전역 변수라면, \(\texttt{static}\) 키워드를 꼭 사용하자!)
3. No Linkage Static Duration (링크를 가지지 않는 정적 존속 시간)
- 특정 블록 안에서 \(\texttt{static}\) 키워드를 이용해 선언해서 정의할 수 있다.
- 정적 지역 변수는 프로그램이 시작될 때 한 번만 초기화되고, 그 이후에 루프나 재귀를 통해 나타나는 정적 지역 변수 초기화 구문은 무시된다. (이러한 특징은, 누계를 계산할 때 굉장히 유용하게 사용된다.)
Ex. 정적 지역 변수를 이용한 누계 계산 예시
void strCount (const char* str) {
using namespace std;
static int total = 0; // 정적 지역 변수 total은 0으로 초기화 된 이후, 다시 초기화되지 않는다.
// strCount() 함수가 여러 번 호출되어도, 위 초기화 구문은 프로그램 실행 시에, 단 한 번만 수행되고
// 그 이후에는 무시되는 구문이다.
int count = 0;
cout << "\"" << str << "\"에는 ";
while (*str++)
count++;
total += count;
cout << count << "개의 문자가 있다." << endl;
cout << "지금까지 총 " << total << " 개의 문자가 입력되었다." << endl;
return;
}
...
// total이 초기화 구문에 의해 0으로 초기화 된 후,
strCount("Hello"); // total에 5가 누산되고, 초기화 구문은 실행되지 않는다.
strCount("What`s up"); // total에 14가 누산되고(5+9), 초기화 구문은 실행되지 않는다.
* 블록 외부에서의 \(\texttt{static}\) 키워드 유무는 외부/내부 링크를 결정하고, 블록 내부에서의 \(\texttt{static}\) 키워드 유무는 기억 존속 시간을 결정하는 모습에서 Keyword Overloading(키워드 오버로딩)이라는 용어가 탄생했다.
* 정적 변수 초기화
- 정적 변수는 Zero-Initialization(제로 초기화), Constant-Expression Initialization(상수 표현 초기화), Dynamic Initialization(동적 초기화)가 가능하다.
- 정적 초기화는 제로 초기화와 상수 표현 초기화를 아우르는 용어이며, 컴파일러가 파일(Translation Unit)을 처리할 때 변수가 초기화된다는 의미이다.
- 모든 정적 변수는 우선적으로 제로 초기화가 시행된다. (따로 명시된 초기화 과정이 있다 하더라도)
- 동적 초기화는 해당 변수가 이후에 초기화됨을 의미한다.
#include <cmath>
int x; // 제로 초기화
int z = 13 * 13; // 제로 초기화 -> 상수 표시 초기화
const double pi = 3.0 * atan(1.0); // 제로 초기화 -> 동적 초기화
- C++11에서 새로 추가된 \(\texttt{constexpr}\) 키워드를 통해 상수 표기를 조금 더 다양화 시킬 수 있다.
* Scope Resolution Operator \(\texttt{::}\) (사용 범위 결정 연산자 \(\texttt{::}\))
- 지역 변수명이 전역 변수명과 겹칠 경우에, 지역 변수가 전역 변수를 Hide 하게 되는데, 이런 상황에서 전역 변수를 사용하고자 할 때, 전역 변수명 앞에 \(\texttt{::}\) 연산자를 붙이면 전역 변수를 사용할 수 있다.
Ex. 사용 범위 결정 연산자 사용 예시
int Global = 135;
int main() {
...
int Global = 246;
std::cout << ::Global; // 이 경우에, 135가 출력된다!
...
}
- 전역 변수는 접근이 쉬운편에 속하는데, 접근이 쉬운 것은 프로그램의 신뢰성을 떨어뜨리며, 이러한 불필요한 접근을 잘 막으면 막을수록 데이터의 Integrity(무결성)가 보전되는 양상을 보인다. 데이터를 무차별적으로 전역 변수로 선언하기 보다는, 지역 변수로 만들고 필요한 함수에만 전달할 수 있게 허용하는 것이 좋다. 전역 변수는 상수 데이터 정도를 나타내는 데 유용하다.