Skip to main content

[C++ Primer Plus] Chapter 9. 메모리 모델과 이름공간

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);



동적 메모리 #

  • 동적메모리는 사용 범위나 링크 규칙이 아닌, newdelete에 의해 관리된다.
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;
};