[C++ Primer Plus] Chapter 9. 메모리 모델과 이름공간
Table of Contents
C++ 기초 플러스 책을 읽고 공부한 노트입니다.
분할 컴파일 #
형식 | 설명 |
---|---|
#include < ... > |
컴파일러가 표준 헤더 파일이 들어 있는 호스트 시스템의 파일 시스템 영역에서 찾는다. |
#include " ... " |
컴파일러가 현재 작업 디렉토리나 소스 코드 디렉토리에서 찾는다. 그곳에서 찾지 못하면 표준 위치에서 찾는다. 따라서 사용자가 만든 헤더 파일을 포함할 때는 큰 따옴표를 사용해야 한다. |
- 서로 다른 컴파일러는 하나의 동일한 함수에 대해 다르게 장식한 이름을 만들어 낸다.
- 따라서 컴파일 된 모듈을 링크시켜야 할 때는 각각의 목적파일이나 라이브러리들이 같은 컴파일러로 만들어진 것인 것 반드시 확인해야 한다.
- 헤더 파일은 단 한 번만 포함시켜야 하므로 다음과 같이 중복을 방지할 수 있다.
#ifndef MYHEADER_H_
#def MYHEADER_H_
// ...
#endif
기억 존속 시간 (storage duration) #
- C++은 네 가지 유형으로 데이터를 저장한다. 이 네 가지 유형은 메모리에 데이터를 존속시키는 시간에서 차이가 난다.
유형 | 설명 |
---|---|
자동 기억 존속 시간 (automatic) | 함수 정의 안에 선언된 변수. 함수 안에서만 유효하다. |
정적 기억 존속 시간 (static) | 함수 바깥에서 선언된 변수, 혹은 static 키워드로 선언된 변수. 프로그램 실행 중에 유효하다. |
쓰레드 존속 시간 (Theard) | 여러 작업을 동시에 실행할 수 있는 CPU(멀티코어 프로세서)를 사용해서 연산 작업을 쓰레드 단위로 쪼개서 처리한다. |
동적 기억 존속 시간 (dynamic) | new를 통해 할당되고, delete를 통해 해제된다. |
사용범위 (scope) #
- 어떤 이름이 하나의 파일(번역 단위) 안에서 얼마나 널리 알려지는가를 의미한다.
유형 | 설명 |
---|---|
지역 사용범위 = 블록 사용범위 (local) | 그 변수를 정의한 블록 안에만 알려진다. |
전역 사용범위 = 파일 사용범위 (global) | 파일 전체에 알려진다. |
함수 원형 사용 범위 | 함수 안에서만 알려진다. |
클래스 사용 범위 | 클래스 안에서만 알려진다. |
이름 공간 사용 범위 | 이름 공간 안에서만 알려진다. |
링크 (linkage) #
- 서로 다른 파일(번역 단위)들이 이름을 공유하는 것을 말한다.
유형 | 설명 |
---|---|
외부 링크 (external) | 여러 파일들이 이름을 공유할 수 있다. |
내부 링크 (internal) | 한 파일 안에서 이름을 공유할 수 있다. |
링크 없음 | 한 블록 안에서만 이름을 공유할 수 있다. |
네 가지 종류의 변수들 #
분류1 | 분류2 | 기억존속시간 | 사용 범위 | 링크 | 선언 방법 |
---|---|---|---|---|---|
자동 변수 지역 변수 (local) |
자동 | 지역 | 없음 | 블록 안에서 선언 | |
정적 변수 | 외부 변수 전역 변수 (global) |
정적 | 전역 | 외부 | 어떤 블록에도 속하지 않는 바깥에 선언 |
정적 변수 | 정적 | 전역 | 내부 | 어떤 블록에도 속하지 않는 바깥에 선언 + static 키워드 | |
정적 변수 | 정적 | 지역 | 없음 | 블록 안에서 선언 + static 키워드 |
1. 자동 변수
- 블록 안에서 선언된 변수이다.
auto
키워드는 자동 변수에만 사용할 수 있다.register
키워드는 C++11이전에 레지스터를 사용해서 자동 변수를 저장함으로써 좀 더 빠르게 접근하게 만들기 위해서 나온 것이었다.- 스택과 두개의 포인터를 사용해서 관리된다. 포인터 중 하나는 스택의 시작점인 바닥을, 하나는 스택의 다음 메모리 저장위치인 꼭대기를 가리킨다. 함수가 호출되었다가 종료되면 꼭대기를 가리키는 포인터가 이전 위치를 지시한다. 값들은 지워지지 않는다.
2. 정적 변수
- 정적 변수를 명시적으로 초기화하지 않으면, 컴파일러가 자동으로 0으로 초기화한다.
2-1. 전역 변수
- 어떤 블록에도 속하지 않는 바깥에 선언
- 여러 파일에서 똑같은 이름의 전역 변수를 사용한다면
extern
키워드를 넣어준다.
// 파일 1에서 전역 변수 num을 선언한다. extern을 생략해도 된다.
extern int num = 20;
// 파일 2에서 전역 변수 num을 사용하기 위해 extern 키워드를 사용한다.
extern int num;
void Global()
{
extern int num; // 함수 안에서 선택적으로 재선언해서 전역 변수를 사용 할 수도 있다.
}
void Local()
{
int num = 10; // 지역 변수가 전역 변수를 가린다.
cout << ::num; // 전역 변수 사용을 위한 사용 범위 결정 연산자 ::
}
2-2. 내부 링크 정적 변수
- 어떤 블록에도 속하지 않는 바깥에 선언 +
static
키워드 - 서로 다른 파일에서 같은 이름으로 각각 다른 변수를 만들 수 있다.
// 파일 1에서 전역 변수를 선언한다.
int num = 10;
// 파일 2에서 내부 링크를 가진 정적 변수를 선언한다. 이 파일에서만 쓰는 또 다른 변수이다.
static int num = 14;
2-3. 링크 없음 정적 변수
- 블록 안에서 선언 +
static
키워드 - 프로그램이 시작할 때 한번만 초기화 된다.
- 함수가 호출될 때 누적되는 값으로 사용할 수 있다.
void Function()
{
// 블록 안에서 링크가 없는 정적 변수를 선언한다.
static total = 0;
total += 1;
}
CV-제한자 #
const
- 메모리가 초기화 된 후에는 프로그램이 그 메모리를 변경할 수 없다.
- 특이한 점은,
const
전역변수는 외부링크가 아니라 내부링크가 디폴트라는 점이다. - 따라서 외부링크인
const
전역변수를 만들고 싶으면extern
키워드를 사용한다.
// 파일 1에서 const 전역변수를 extern 키워드를 사용해서 선언한다.
extern const int states = 10;
// 파일 2에서 states을 사용하기 위해서 extern 키워드를 사용한다.
extern const int states;
volatile
- 외부적인 요인으로 그 변수의 값이 언제든지 바뀔 수 있음을 뜻한다. 컴파일러의 최적화를 막아서 레지스터에 로드된 값을 사용하지 않고 매번 메모리를 참조하게 만든다.
- 같은 변수를 여러 번 대입하면 마지막 대입만 의미 있다. 따라서 컴파일러는 나머지 앞의 불필요한 구문을 누락시킴으로써 최적화한다.
volatile
제한자는 이러한 최적화를 막는다.
기억 공간 제한자 #
auto
(C++11에서는 의미 변경)
auto int a = 3; // (X) auto는 더 이상 자동 변수라는 의미의 선언이 아니다.
auto b = 3; // (O) auto는 자동 형변환에 사용한다.
register
(C++11에서는 의미 변경)- 변수를 RAM이 아니라, 가능한 Register에 저장하도록 요청한다.
- C++11에서는 단순히 자동 변수임을 명시하는 방법이다.
static
- 바깥에 선언하면 내부 링크, 블록 안에 선언하면 링크 없음.
extern
- 다른 파일에 존재하는 전역변수를 사용할 수 있게 한다.
thread_local
(C++11에서 추가됨)thread_local
키워드로 선언한 변수는 그 존속 시간이 변수를 포함하는 쓰레드의 존속 시간이 된다.
mutable
- 특정 구조체나 클래스가
const
로 선언되어 있더라도 그 안의 특정 멤버를 변경할 수 있음을 나타낸다.
- 특정 구조체나 클래스가
함수 #
-
정적 기억 존속 시간, 외부 링크
-
static
키워드로 내부 링크를 부여할 수 있다. 이것은 함수의 사용 범위를 파일 하나로 제한한다.
// static 키워드로 내부 링크를 가진 함수를 만든다.
static void Function(); // 함수 원형
static void Function() {} // 함수 정의
- 함수는 모두 하나의 정의만을 가진다. 하지만 인라인 함수는 각각의 파일에서 모두 인라인함수의 정의를 가진다.
언어 링크 #
-
각 언어의 컴파일러는 각기 다른 방식으로 함수들의 이름을 장식한다.
-
만약 C++프로그램에서 C 라이브러리의 미리 컴파일된 함수를 사용하고 싶다면, 사용할 프로토콜을 알려주는 함수 원형을 사용할 수 있다.
extern "C" void Function(int);
동적 메모리 #
- 동적메모리는 사용 범위나 링크 규칙이 아닌,
new
와delete
에 의해 관리된다.
int * ptr = new int;
- 4바이트의 메모리가
new
에 의해 대입된다.delete
에 의해 해제될 때까지 메모리에 유지된다. - 반면
ptr
은 선언된 함수가 종료될 때 사라진다.
new
연산자를 이용한 초기화
int * ptr = new int (10);
int * ptr = new int {10};
int * ptr = new int[5] {10, 11, 12, 13, 14};
-
new
는 필요한 메모리 양을 확보할 수 없는 경우std::bad_alloc
예외를 반환한다.
위치 지정 new #
- 사용할 메모리를 프로그래머가 지정할 수 있는
new
이다.
#include <new>
struct StructSample
{
// ...
};
char buffer[50];
// 정적 메모리인 buffer를 사용한다. delete는 안 한다.
StructSample * ptr = new (buffer) StructSample;
이름 공간 #
-
새로운 종류의 선언영역을 정의하는 것이다.
-
전역위치나 다른 이름 공간 안에 놓일 수 있다. 하지만 블록 안에는 놓일 수 없다.
-
기본적으로 외부 링크를 가진다.
namespace Jack
{
int jackNum = 10;
int isJackHome = false;
}
Jack::jackNum = 20; // 사용 범위 결정 연산자 ::를 사용해서 이름공간 안에 이름을 접근한다.
using 선언과 using 지시자 #
using 선언
using 선언
으로 특정 이름을 선언된 영역에 추가할 수 있다.
using Jack::jackNum; // using 선언
jackNum = 20;
int jackNum = 10; // (X)
using 지시자
using 선언
이 하나의 이름만을 사용할 수 있게 만든다면,using 지시자
는 모든 이름을 사용할 수 있게 만든다.using 선언
은 그 위치에 그 이름을 선언하는 것이라면,using 지시자
는 해당 범위에namespace
를 넣는 것이다.- 따라서 똑같은 이름을 이후에 재선언했을 때 그 전에
using 선언
을 했다면 에러가 나지만,using 지시자
의 경우에는namespace
의 변수를 가리기만 한다. 따라서 일반적으로using 선언
이 좀 더 안전하다.
using namespace Jack; // using 지시자
jackNum = 20;
isTrue = true;
int jackNum = 10; // (O)
이름공간의 대용이름 #
namespace J = Jack; // 이렇게 대용이름을 만들 수도 있다.
using J::jackNum;
jackNum = 20;
명명하지 않은 이름공간 #
- 다른 파일에서 사용할 수 없다. 마치 내부링크를 가진 정적변수와 같다.
namespace // static int count;와 같다.
{
int count;
};