Chapter 9. Memory Models and Namespaces

지환·2022년 6월 28일
0

Separate Compilation

C에서처럼 여러 source file을 만들 수 있고, 얘네는 각각 compile된다.
linking을 통해 executable program을 만드는건 별도이다.
따라서 특정 source file을 수정했다면, 그 file만 compile해서 다시 linking하면 된다.
UNIX나 LINUX에선 make란 프로그램을 통해 이런 과정(수정되면 다시 compile하고 linking하는 과정)이 어느정도 자동화 될 수 있다.(C에선 makefile이었는데..)
Microsoft의 visual C++, Apple의 Xcode 등 다양한 IDE들도 이런 기능을 지원한다.
compile 과정은 implementation specific이므로, design같은 일반적인 측면을 보자.

1) structure declarations과 그것을 사용하는 함수의 prototypes을 포함하는 header file
2) structure와 연관된 함수 code들을 포함하는 source file
3) 그 structure와 연관된 함수들을 호출하는 code가 있는 source file
이렇게 모든 프로그램을 나눌 수 있다.(뭐 이 경우는 strucutre 중점으로 보긴 함)
이게 OOP 접근법과 관련된다. 한 file에 user-defined type을 정의하는 것(knk19에서 ADT 직접 만들어봄)

header file에 definition이 오면 문제가 생길 수 있어서, 거의 아래 요소들이 주로 포함된다.
1. Function Prototypes
2. Symbolic constants defined using #define or const
3. Structure declarations : 변수를 안만들고, struct type의 선언만 있는건 괜찮다.
4. Class declarations
5. Template declarations
6. Inline functions : 일반적인 함수 definition이 오면 error 발생 가능성이 있다.(크다.)

const variable과 inline function definition은 특별한 linkage properties를 가지므로 괜찮은 것

UNIX나 LINUX에는 CC file1.cpp file2.cpp 하게되면 자동으로 두놈 compile하고, linking해서 executable file 만들어준다.
다른 OS의 IDE에서도 본질적으론 같은데, 얘네는 source file들을 같은 project에 넣어줘야 할 수도 있다.
(그게 어찌보면 linking인 셈..인거지뭐 unix에선 CC 할때 같이 적으면 같이 linking되고, 얘네는 같은 project에 넣어야 linking 되는거고..)

우리는 file이라고 했지만, C++ standard는 generality를 위해 translation unit이란 용어를 쓴다.
(file이 computer information을 표현할 수 있는 유일한 용어가 아님)
(보통은 file이라고하나 translation unit이라고하나 그게 그거임)

C++ standard에서는 compiler designer가 name decoration(mangling)을 자유롭게 구현하도록 한다.
그렇기때문에,
다른 compiler에 의해 만들어진 binary modules(object-code files)끼리는 linking이 안될 수도 있다.
그렇기때문에 compile된 module들을 linking할때,
각 object file이나 library가 같은 compiler에 의해 만들어졌다는걸 확인해야한다.

name decoration이라 함은 compile시에 함수 이름을 internal representation으로 변경하는 것이다.
ex) long MyFunctionFoo(int, float); -> ?MyFunctionFoo@@YAXH

정리

정확하게 딱 어디 정리된건 없지만 knk나 이 책 참고하고 기본 지식들 바탕으로 정리
(잘못알고 있던 것들 좀 바로잡힘)

한 프로그램을 만들기 위한 file이 여러 개로 나뉘어져 있을때 문제가 될 수 있는 경우들은 다음과 같다.

  1. 특정 variable이나 function이 external linkage인데, 같은 identifier를 가지는 definition이 전체 file에 여러 개 존재할 때 문제가 된다.
    : external linkage인데 file a에도 definition이 있고, file b에도 definition이 있으면 당연히 그 둘은 충돌하겠지(signature가 달라서 overloading이 된게 아니라면,,)
    함수의 경우 inline이 아니라면 error이다.(p.448)
    typedef는 scope rule을 따르기 때문에 괜찮을 것(scope rule에는 함수간 공유는 없음 file scope가 최대)

  2. 같은 file 내(internal linkage, no linkage)에서도 variable이나 function의 definition이 여러 개 존재할 때 문제가 된다.
    : 얘도 당연히 definition이 같은 linkage 안에 여러개 있으면 충돌하겠지
    이 경우 typedef는 여러 개 존재해선 안됨.(C++도 그런가?)

해결 방법

  1. 의 경우를 막기 위해 우리는 header엔 함수든 변수든 definition말고 declaration만 작성하는 것이고, source file(declaration이 있지 여기엔)은 include하지 않는다는 convention을 가지는 것이다.

  2. 의 경우, C/C++은 include guard라는 것을 한다. conditional compilation 중 #ifndef, #define, #endif를 활용한다.(knk에서 이미 배움)
    왜 include guard를 사용하지? 1번 해결 방법대로만하면 굳이 multiple inclusion을 막을 이유가 메모리 낭비된다는 것 말고는 없지 않나?
    : knk에 의하면 typedef 때문이다.
    여러 file에 같은 typedef가 존재하는건 상관없는데, 같은 file에 두번 나오는건 안된다.
    다른 문제들은 1번 해결방법으로 해결되지만, typedef 때문에 multiple inclusion이 문제가 될 수 있어서 이를 막는 것이다.

include guard 개념 바로 잡기
: 링크를 보다가 내가 좀 잘못알고있는게 있는 것 같아서 여기 내용들 정리해본다.

1) (이거는 p.451 내용) include guard를 하면, include 자체를 막는 것이 아니라, 다음 inclusion을 무시하는 것이다.
위 링크의 첫번째 질문에 해당하는 내용인데, 두 헤더가 서로 include하게 되면 무한히 include 하게 수도 있다. infinity loop 같이..
왜 그런가하면 include guard가 있더라도 include는 일단 일어나기 때문이다..
안의 code가 무시될 뿐이지 header file 자체는 include된다.

2) header file에 include guard가 있다고해서 그 header file에 함수/변수 definition 넣고 막 include 할 수 있단 뜻이 아니다.
include guard는 한 file에서 특정 header file이 두번 이상 include되는 걸 막는 것이다.
위 링크의 두번째 질문에 해당하는 내용인데, include guard가 있더라도 함수 definition이 들어있는 header file을 두곳 이상에서 include하고 compile linking하면 오류가 뜬다.
왜냐하면 두 file 모두에 해당 header가 include돼서 program 내에 definition이 두개 이상 생겨버렸기 때문이다.
자세히 보자면,, compile이 되는 과정은 이렇다.
한 translation unit(file)에 대해 preprocessing 진행하고, object code로 변경한 후 다음 translation unit으로 간다. 이때 preprocessing의 정보들은 초기화된다.
즉, include guard는 한 file내에서만 소용있단 소리다.
당연한 것이 우리가 특정 library header를 file a에 포함시키면, 같은 project 내의 다른 file b는 그 header를 못쓰나?아니다.

정리하자면, "header에 definition 안쓰고 source file include 하지 않는 규칙"이 있더라도 "include guard"는 있어야 하고, 반대로 "include guard"가 있더라도 "해당 규칙"은 지켜져야한다. 하나가 하나를 커버하지 못한다.
해결 방법에 있는거 둘 다 써라.

https://stackoverflow.com/questions/7059800/why-do-structure-definitions-have-internal-linkage
질문글 같은 고민이 들 수도 있는데,
structure definition이나 class template은 no linkage이다.
linkage는 object나 functions만 가지는 개념이다.

Storage Duration, Scope, and Linkage

Storage Durtion

  • Automatic storage duration
    function definition 내(parameter 포함)에서 정의된 variables
    program execution이 함수로 들어올때 define되고, 나갈때 freed

  • Static storage duration
    function definition 내에서 정의된 variables
    혹은 static keyword를 사용해 정의된 variables

  • Dynamic storage duration
    new를 사용해 정의된 경우
    delete operator를 만나거나 프로그램이 끝날때 freed
    free store 혹은 heap 이라고도 한다.

  • Thread storage duration (C++11)
    요즘 multicore processor는 흔하다. 얘네는 여러 execution을 동시에 진행한다.
    그래서 우리는 program도 thread 로 찢을 수 있다. (thread란 동시에 진행될 수 있는 것)
    thread_local keyword를 사용해 정의하면, thread가 유지되는 한 얘의 memory도 유지된다.
    (이 책에선 concurrent programming은 크게 안 다룸 <- 나중에 이 부분은 더 공부해야겠군)

Scope and Linkage

  1. local scope(block scope) : block 내에서 visible, 선언된 point 이후로 visible
  2. global scope(file scope) : file 내에서 define된 point 이후로 visible
  3. function prototype scope : 함수 argument list의 praentheses 내에서 쓰인 경우
  4. class scope : class 내에서 선언된 members (Ch 10)
  5. namespace scope : namespace 내에서 선언된 variables
    (C++ langauge에 namespace가 추가된 이후로, global scope(file scope)는 namespace scope 중 특수한 경우이다.)

C++ 함수는 class scope일 수도 있고, namespace scope일 수도 있는데, block 내에서 define될 수 없기 때문에 local scope일 수는 없다.

앞부분 내용들 C랑 거의 같아서 적을만한 것들만 적으면서 가야지

Automatic Storage Druation

Automatic Storage Duration/Local Scope/No Linkage

no linkage이므로, 함수 A에서 정의된 변수 a와 함수 B에서 정의된 B는 서로 영향을 주지 못한다. 따로 정의됨.

variable은 execution이 block에 들어올때 allocated는 되지만, scope는 declaration 이후로 시작된다.

안쪽 block에서 같은 이름으로 선언된다면, 기존의 identifier는 hide된다.
(컴파일러는 어떻게 이걸 구분하려나,, C 표준 보니까 "같은 point에서 scope가 끝날때만 같은 scope라고한다"던데 뭐 이거랑 no linkage인 점 활용해서 어쩌어찌 하지않을까싶네 어쨌든 난 그냥 이정도로만 알고 써먹는덴 문제없을듯)

Register Variables

C++11 이전에 register keyword를 사용하면 (거의) C에서처럼 CPU register로 사용하도록 suggest하는 것이었다.
C++11 이후로는 그냥 변수가 automatic이라고 명확하게 기술하는 정도로 바뀌었다.
(예전 auto의 역할)

이전처럼 register에 저장하는 것에 대한 hint만 주는 걸으로는 이제 사용되지 않는다.
그렇다고 이전 code와의 호환을 위해 이 keyword 자체를 없애버릴 순 없어서 이렇게 한 것

Static Duration Variables

program 돌아가는 내내 유지돼야하므로, automatic varialbes처럼 stack 같은걸 사용할 필요가 없다.

`static` keyword가 어떤 variable에 붙냐에따라 의미가 달라진다.
이걸 "keyword overloading" 이라고도 한다네..

Initialization

1) static initialization
: compiler가 translation unit(file)을 진행할때 변수가 초기화됨. 아래 2가지 경우로 나뉜다.

  1. zero-initialization
    : "실제값"을 0으로 setting시킨다.
    structure나 배열은 모든 bit가 0으로 되고, structure는 padding bit(holes)도 0으로 setting된다.
  2. constant expression initialization

2) dynamic initialization
: C에서는 static initialization만 허용했었는데, C++은 compiling 중이 아니라 나중에 초기화 되는 것도 허용
ex. initializer에 함수호출 등을 포함해도 OK

Q. dynamic이 어떻게 되지? static이면 시작할때부터 초기화돼야되지않나..
A. static variable은 일단 무조건 zero-initialization으로 시작한다.
일단 0으로 다 초기화시키고, 그 다음 상황에 맞게 constant expression이나 dynamic 초기화를 하는 것.

classic K&R에선 automatic arrays나 structures를 초기화하도록 허용하지 않았었다.
old C++ compiler도 아직 그럴 수 있으니,
그럴땐 해당 array나 structure가 static storage duration을 가지도록 해줘야한다.

constexpr (C++11)

constant expression..
(이 책에선 자세히 설명 안하겠다고 함)

The One Definition Rule (ODR)

static storage duration, file scope, external linkage인 변수를
"external variable" or "global variable" 이라 한다.

말그대로 하나의 definition 밖에 올 수 없단 뜻이다.
그렇다고 해서 같은 이름들 전체에서 하나의 definition만 올 수 있단 뜻이 아니라, 같은 이름이더라도 각 version별로 하나의 definition이 올 수 있단 뜻이다.
(inline이 아닌 함수에 대해서도 마찬가지이다. p.475)
예외(?)도 있다. 같은 정의가 두번 등장하는 것은 괜찮다. 즉, int x=5;가 여러 파일에 등장하는 것은 괜찮다.
linker가 어떤 정의와 combine할지 애매한 것 때문에 ODR을 지키는 것이므로, 같은 정의가 여러 번 있는 것은 상관없다.
(참고로 ODR은 namespace scope에 정의된 function이나 object에만 적용된다. 따라서 class scope 같은데선 적용 X)
(조금 다른 얘기긴한데 상기시켜보자면, 마찬가지로 linker가 관여하는 linkage 개념도 function이나 objec에만 적용됐었음.)

변수에 extern을 안붙이거나, extern을 붙여도 initialize를 한다면, defining declaration(definition) 이라 하고,
extern만 붙이면 referencing declaration(declaration) 이라 한다.
(후자는 저장공간 할당 받는게 아니라 존재하는 변수를 지정한다.)

scope-resolution operator(::)
이게 변수 이름 앞에 쓰이면 해당 변수의 global version을 사용하라는 의미
(scope rule에 의존해서 막 이리저리 하는 것보단 이렇게 하는게 더 간편하고 안전하다고 하네)

const char * const moths[12] = {~};
첫번째 const는 각 strings이 바뀌는걸 막아주고,
두번째 const는 각 포인터(elements)가 다른 곳을 가리키도록 바뀌는걸 막아준다.

Static Storage Duration, Internal Linkage

file scope identifier에 static으로 선언하면 internal linkage가 되고,
이러면 external definition을 가져오려는 시도를 하지 않으므로 external version이 있더라도 둘은 충돌하지 않는다.

Static Storage Duration, No Linkage

block 내에 static으로 선언
automatic variables처럼 함수 호출마다 매번 초기화되지 않고, program 시작시 한번만 초기화 된다.


Specifiers and Qualifiers

Storage Class Specifiers

  • auto (C++11부턴 spcifier에서 삭제)
  • register
  • static
  • extern
  • thread_local
    로 선언된 변수의 duration은 포함된 thread의 duration과 같다.
    일반 static 변수가 전체 프로그램에 대한 것이듯 얘는 thread에 대한 것이다.
  • mutable
    structure나 class의 특정 member를 mutable로 선언한다면,
    해당 structure나 class가 const로 선언되더라도, 그 멤버는 변경할 수 있다.

thread_localstatic이나 extern과 쓰일 수 있단 걸 제외하면, 위 specifier들은 하나씩만 쓰일 수 있다.

Cv-Qualifiers (cv는 const와 volatile의 앞글자../ 검색해봐도 C++은 restrict 없는듯?)

  • const
    initialize 이후 수정 불가
  • volatile
    특정 memory location은 program에서 건드리지 않았는데도 변경될 수 있다.
    예를들어 시간이나 port 정보를 나타내는 부분은 program이 아닌 hardware가 값을 변경한다.
    이런 값이란걸 알려주는게 volatile이다.
    적은 statement 사이에 두 값을 사용한다고 해보자. comiler는 두 값이 프로그램 내에서 변경되지 않는다면 같을 것이라고 보고 매번 메모리에 접근하기보다 register에 올려버리는 최적화를 할 수도 있다.
    만약 위에서 말한 time을 나타내는 부분이라던가 하면 문제가 되기때문에 volatile을 이용해 최적화를 막는다.

const의 linkage에 대해

(C에서와 다르게) C++에서 const global variableinternal linkage를 기본으로 갖는다.
(global variable일때만 그런거임. block에서 쓰이면 상관없다.)

원래대로 external linkage로 쓰고 싶다면
extern const int states = 50; 식으로 extern을 붙여주면 된다.
이러면 external linkage + definition이 되는 것.
(뒤에 =로 값도 초기화 해줘야 definition이 되겠지 당연히?)

rule을 이렇게 좀 변경한 이유는 다음과 같다.
const 변수를 header file에 넣고, 그 constant가 필요한 file은 그걸 include하도록 한다고 해보자.
두곳 이상에 include하면 충돌한다. 해결하려면 기존 변수에 하듯이 한곳에만 definition, 다른곳엔 extern으로 해야함.
따라서 const 변수는 default가 internal linkage이도록 한 것이다.

Why??
C와 달리 C++에선 #define 대용으로(오히려 #define보다 더 권장하며) 쓰이니 이렇게 해주는 듯?
#define처럼 header에서 여러곳 incldue돼야하니..
#define은 file별로 처리되는 preprocessing 과정이라 상관없었지.
같은 이름도 정의가 같으면 또 와도 상관없었고.

Functions and Linkage

(C와 마찬가지로) 함수를 다른 함수 내에서 define하는건 허용하지 않는다.
따라서 functions은 기본적으로 static storage duration + external linkage
그래서 다른 곳에 정의된 함수를 가리키기 위해 prototype을 선언하며 extern을 붙여도 되고, 안붙여도 된다.
internal linkage로 만들고싶으면 마찬가지로 static을 사용 (prototype과 definition 둘 다에 붙이기)

inline 함수는 ODR에서 제외되지만, 대신 특정 함수의 모든 inline definition은 같아야 한다는 제약이 있다.

Language Linking

linker는 각 함수에 대해 서로 다른 symbolic name이 필요하다.
C에선 좀 쉬웠다, 이름 하나에 함수 하나밖에 없었으니 간단하게 spiff란 함수를 _spiff 이런 식으로 이름 지으면 됐다. 이를 C language linkage 라고 한다.
C++에선 같은 함수 이름이어도 여러 함수가 올 수 있다. 그래서 name decoration(name mangling)이란 것을 했다. 이를 C++ language linkage 라고 한다.

즉, C와 C++에서 linker가 함수를 찾는 방식이 다르다.
그럼 만약 미리 compile된 C library의 function을 C++ 프로그램에서 사용하고자 한다면?
이를 해결하기위해 아래와 같이 prototype에 어떤 language linkage를 사용할지 기술해줄 수 있다.
extern "C" void spiff(int); : (definition의) 이름을 찾을때 C방식 사용
extern void spoff(int); : C++ 방식 사용 (이게 default)
extern "C++" void spaff(int); : C++ 방식 사용

syntax 찾아보니 앞에 extern은 필수

Dynamic Allocation

C++ new operator나 C의 malloc을 사용한 저장공간은 scope나 linkage rule이 적용되지않고 오로지 newdelete에 의해 다뤄진다.
그렇기때문에 할당과 해제가 아무 곳에서나 가능한 것이다.
그런 곳을 dynamic memory라 부른다.

그 곳을 가리키는 포인터 변수는 물론 scope나 linkage rule을 따른다.
그렇기때문에 다른 곳에서도 사용하려면 함수에서 반환해주던가, extern으로 공유하던가.. 해야한다.

Initialization with the new Operator

int *pi = new int (6); : 6으로 초기화 됨.

기본 built-in type이면 이렇게하는데, structure나 array는 C++11 이상이어야한다.
struct where {double x; double y;}; 일때,
where * one = new where {1.2, 3.3}; : 이렇게 braces를 활용한 list initialization 이용

buit-int 변수여도 braces 사용 가능
int *pi = new int {5}; : 5로 초기화

`new` 실패?
: 초기엔 null pointer를 return 함으로써 이를 처리했다.
요즘은 std::bad_alloc exception을 throw 함으로써 해결한다.

new: 가 사용하는 함수

allocation functions 이라고 함.
void * operator new(std::size_t); : used by new
void * operator new[](std::size_t); : used by new[]

이렇게 delete도 사용하는 함수가 있음.
void operator delete(void *);
void operator delete[](void *);

즉, int * pi = new int; 라고 하면 사실은 int * pi = new(sizeof(int));식으로 바뀌어서 사용된다.
(정확하진 않음)

이러한 함수를 replaceable 이라고 하는데, 만약 전문지식이 충분하고 원한다면 new와 delete의 replacement functions을 목적에 맞게 직접 제공할 수 있다.

The Placement new Operator

원래는 newheap에서 필요한 공간을 찾아야한다.
하지만 다른 곳에서 필요한 공간을 찾도록 명시해줄 수 있는데 이것을 placement new라고 한다.

#include <new>

char buffer1[50];

이후
int *p1 = new (buffer1) int [10];
이렇게 buffer1이라는 공간에 할당을 받을 수 있다.
이러면 p1buffer1과 같은 주소를 가진다.
재밌는건 저렇게 하고나서 double *p2 = new (buffer1) double[5];을 했을때 p2buffer1과 같은 주소를 갖는다는 것이다.
즉, placement new는 현재 사용되는 메모리인지 keep track 같은거 안하고, 아무 pointer에나 assign될 수 있게 그냥 넘겨받은 메모리 주소를 void *로 바꿔서 반환해줄 뿐이다.
(이건 default placement new이고, overload해서 사용할 수도 있다고 함)

저렇게 할당받은 p1, p2delete해줄 필요도 없다. 애초에 쟤네는 heap에 할당된게 아니므로 delete를 할 수도 없다.
(물론 heap에 할당받은 곳에 placement new를 적용한거라면 delete 할 수 있음)

REMIND) cout에 char*의 값(주소) 그대로 출력하고싶으면 (void *) 로 casting 해야한다.
아니면 char*은 그냥 문자열 출력됨

placement new: 가 사용하는 함수

int * p2 = new(buffer) int; 식으로 사용하면 new(sizeof(int), buffer);가 호출된다.
얘는 일반 new처럼 replaceable이진 않고, 대신 overloading 가능

overload할때는 큰 조건 없이
"최소 2개의 parameters + 첫 parameter가 std::size_t를 입력 받"도록만 overloaded되면 placement new 함수라고 한다.


Namespaces

함수, 변수, 클래스, 구조체 등등.. name 모든 것에 name이 있는데, 프로그램이 커지면 충돌 가능성 UP
보통 include할땐 declaration들만 include하고, 더해봐야 internal linkage인 definition이다.
그러더라도 일단 include하는 곳 각각에 있는 두 함수 identifer가 같은데 signature도 같다면(overload안됨) 둘은 충돌할 것이고, identifier가 같은 internal linkage를 양쪽에서 include한다면 이것도 충돌한다.
그래서 C++ standard에선 namespace를 통해 name의 scope를 더 광범위하게 컨트롤한다.

결국 C++도 C위에 만들어졌다. C엔 없던 namespace가 어떻게 구현된걸까..
자세한 detail은 모르겠지만 이런 방법으로 구조체를 namespace처럼 활용해서 구분할 수도 있고,
뭐 그냥 namespace 이름하고 그 안의 identifer가 ## operator 적용된 것처럼 바뀌게 해볼 수도 있을 것 같기도 하고..
(그런데 전자의 경우 각 definition들을 각 file에 static으로해서 충돌 안되게 해야되니까 오히려 후자가 개념적으론 더 맞을듯? C++ namespace에서 보면 애들 external linkage가지도 해도 상관없으니까. 여기보면 name mangling도 namespace를 구현하는 한 부분이라고 함, 근데 구현이야 설계자 나름이긴하지..)

어떻게 구현될지 정도는 위처럼 생각해볼 수 있지만, 그렇다고 아래 scope로 설명하는걸 거부하진 말자.
결국 표준에도 name mangling 같은 implementation specific인 내용은 나오지 않는다.
(보니까 mangling은 함수 오버로딩 signature 얘기할때만 나오네)
C에서의 이름충돌 문제를 피하기위해 만든 특징인데, C에서는 결국 이름을 길게 쓰는 방법밖에 없었다.
그게 이상적인 방법은 아니었어서 C++에선 (1)global scope를 'namespace'라는 것으로 분할함으로써 해결한 것이다.
scope만으로는 external linkage일때 이름 충돌을 모두 해결할 수는 없으니 (2)namespace scope의 애들은 :: 연산자로만 접근할 수 있도록하는 것까지 추가해서 완벽하게 해결한다.
내부적으로 이름 변경만 하면 다 해결되지않나.. 라고 생각할 수 있는데, 위 방식대로 기존의 scope 개념을 확장하는 편이 훨씬 더 깔끔하고 잘 적용된다. 같은 scope내부에선 :: 없이 적을 수도 있고,, 위 방식대로 해도 문제도 없다.
구체적 내부 구현사항은 표준에 적혀있지도 않고,, 뭐 어차피 name mangling이 들어간다고는 하지만 그건 뭐 알고만있고, 위 내용대로 따라가는게 맞는 것 같다.
링크

Traditional C++ Namespaces

declarative region : declaration이 발생할 수 있는 region
potential scope : declaration 지점으로부터 declarative region 끝까지
scope : potential scope 내에서도 다 보이는게 아니다. 실제로 그 변수가 보이는 program의 특정부분을 scope라고 함.

declarative region이 다르다면 각각에서 선언되는 변수는 독립적이다.
(declarative region/potential scope/scope 그림은 p.484 485...
declarative region이 다르다는게, file에서 되는거랑 뭐 block들도 서로 구분되고 하니까 다 다른 경우지)

New Namespace Features

namespace를 만드는 것은 새로운 declarative region을 만드는 것이다.
그렇게 함으로써 다른 namespace의 identifer들과 충돌하지 않는다. (일단은 같은 파일 내에서만..ㅎㅎ)

namespace에 global namespace가 포함되는 것이고, namespace는 :: 통해서 접근해야하니
global namespace도 ::통해서 접근. 예전 C에서 하던 방식인 그냥 이름으로 접근하는것도 가능.
뭐 대충 이런식으로 정리되겠네..
namespace Jack {
	double pail;
    void fetch();  //prototype
    struct mo {...};  //strucuture declaration
}  //no semicolon

(뒤에 semicolon 안붙음)

위처럼 직접 만드는 user-defined namespacesglobal namespace로 나뉜다.
후자는 그냥 기존에 있던 file-level declarative region이다.

namespace는 global level이나 다른 namespace내에서만 위치할 수 있다.
(block내 불가/block내의 애들은 이름충돌이 나질 않으니 namespace 필요없음)
그래서 constant가 아니라면 external linkage가 기본이다.(그냥 const로 선언됐으면 당연 internal linkage)

기존에 있는 namespace가 open인 상태라면

namespace Jack {
	void fetch(){ ... }; //function definition가 namespace Jack에 추가됨.
}

이렇게 기존 namespace에 추가할 수도 있다.

Jack::pail = 1.23; 이렇게 namespace의 name에 접근하려면 :: scope-resolution operator를 사용한다.
그냥 pail처럼 쓰인걸 unqualified name이라하고, Jack::pail처럼 쓰인걸 qualified name이라 한다.

using Declarations & using Directives

using Declarations은 특정 identifier를 사용가능하게 만든다.
ex) using Jack::fetch;
이게 쓰인 declarative region에 해당 name이 추가됨, 그곳에서만 그냥 fetch로 사용 가능
(scope나 linkage와 헷갈리면 안된다. 해당 이름은 어차피 사용할 수 있는 범위에 있다면 Jack::을 앞에 붙이면 언제든지 쓸 수 있는 것이다. 그냥 저렇게 선언한 범위에선 prefix없이 사용할 수 있는 것일 뿐이다.)

using declaration을 external level에 위치시킨다면 해당 name은 global namespace에 추가된다.
즉, file내 어디서든 그냥 fetch로 쓰일 수 있단 뜻이다.

using Directives는 위와 다르게 모든 name을 사용가능하게 만든다.
ex) using namespace Jack;
위와 마찬가지로 global level에서 쓰이면 namespace의 name들도 마찬가지로 file내 어디서든 그냥 쓰일 수 있고,
특정 함수 내에서 쓰이면 딱 그 범위에서만 unqualified name으로 쓰일 수 있다.

->주의 사항
using을 남발해선 안된다.
namespace를 통해 앞에 std::식으로 붙이게 함으로써 name conflicts를 줄이려는 것인데, using을 남발해버리면 이 의미를 퇴색시킨다.
굳이 구분할 필요가 없는 상황이라던가 이럴때 사용하는 것이다.
이런 측면에서 using directive보단 declaration이 더 낫다.

->둘의 차이
declaration은 함수 내에 같은 이름으로 쓰인게 있다면 쓰일 수 없다. 둘은 충돌하기 때문이다.
directive는 좀 다른데, local variable 중 namespace내에 겹치는 이름이 있더라도 상관없을 수도 있다.
(declaration은 말그대로 declaration이라 같은 이름 선언되면 바로 충돌하는데, 얘는 쓸때 잘 구분만 하면 충돌은 안하네 해보니까.. UB인가?)
directive 궁금하면 여기 보기..
링크 요약하자면 결론은 using directive는 안쓰는게 맞다. 뭐 그래도 한번 보자면..
declaration은 그 scope에 name이 추가되는데반해, using directive가 쓰인 곳과 해당 namesapce의 공통조상(?)에 name이 추가된다.(간단하게 설명하긴 어렵네. 뭔말인지 모르겠으면 그냥 링크 드가서 자세히 보는거로 합시다)
사실 이것도 추가된다기보다 추가되는 효과를 가진다. directive가 쓰인 scope에서만 unqualified name을 사용할 수 있기 때문이다.

namespace jh {
    int a = 3;  //1
}
int a = 5;    //2
int main () {
    using namespace jh;
    //int a=5;  //3
    cout << a;
    return 0;
}

즉, 이 경우 global 위치에 jh의 name들이 추가되므로 원래 있던 global a와 구분이 안된다.
그래서 ::ajh::a로 구분해주지 않는 이상 오류 발생
main내 a가 주석으로 돼있는걸 활성화하면 각각 잘 작동한다.
해당 line을 활성화한다면, jh::a는 1을 나타내고, ::a는 2를 나타내고, a는 3을 나타낸다.

namespace를 지원하지 않는다면 `#include <iostream>` 대신 `#include <iostream.h>` 사용

추가 namespace 특징

1. nest namespace

namespace animal {
	namespace dog {
    	int a;
        int b = 5;
        double c;
    }
}

animal::dog::a; 식으로 직접 사용하거나,
using nampspace animal::dog; 처럼 안쪽 namespace에만 적용할 수도 있다.

2. namespace 내에서 using 사용

namespace myth {
	using Jack::fetch;
    using namespace animal;
    using std::cout;
}

이 안의 애들은 이제 myth namespace에도 속하게 된다.
그래서 myth::fetch 식으로 직접 사용할 수 있고,
혹은 본래 namespace를 활용해 Jack::fetch로 사용할 수도 있다.
using namespace myth;를 하게되면 저 안의 애들은 당연히 unqualified name으로도 사용할 수 있다.

참고) using directive는 trasitive하다.
그러므로 using namespace myth;를 하면 using namespace animal;은 자동으로 따라오는 효과를 가진다.
즉 저 문장 하나로 animal의 이름들도 그냥 사용할 수 있게 된다.

3. alias for a namespace
namespace MEF = myth; 식으로 alias 생성 가능
nested namespaes를 단순화하는데 사용할 수 있다. namespace MF = myth::fire;

4. unnamed namesapce

namespace {
	int ice;
    int q;
}

이런식으로 namespace에 이름이 없을 수 있다.
이렇게되면 바로 다음에 using directive가 따라오는 것과 같은 효과를 가진다. 즉, 이것이 선언된 declarative region 끝까지의 scope를 가짐.

(namespace는 "함수밖" or "namespace안" 에서밖에 못 쓰이는데, namespace안에서 쓰이는 경우는 빼고보자.)
그러면 함수 밖에서 declarative region까지 범위니까 사실상 global varaible인 셈이다.
즉, static storage duration + external linkage

그런데 unnamed이기 때문에 external linkage이더라도 다른 파일에서 사용하는 것은 불가능하다.(?::ice;)
그러므로 internal linkage와 사실상 같은 기능을 한다.

그래서 unnamed namespace내에서 쓰인 변수는, 함수 밖에서 쓰이는 static int a;같은 static duration+internal linkage인 변수의 대안으로 사용될 수 있다.

언제 static 대신 unnamed namespace를 쓰나?
: class 같은 user defined type은 static keyword를 사용하지 못하기때문에 unnamed namespace가 static에 비해 superior이다.

참고로, 중간에 내가 알던 static keyword 용법을 deprecate시키기로 했던 결정도 있었는데, 이는 번복되었다.(즉, 그냥 원래처럼 잘 쓰인다.) 참고1, 참고2

namespace 실사용

<머릿속 정리>
namespace는 어찌보면 이름 충돌을 막기위해 기존 코드에 이름을 더 복잡하게 만들어야될거를 그냥 namespace내에 둠으로써 덜 귀찮고 덜 복잡하게 하는 것일 뿐이다.
그러므로 namespace내에서 다른 namespace 안에 들어있는 identifier를 사용해야한다면 using쓰거나 :: 쓰거나 하면 되고, 그냥 일반적인 code 작성하듯이 하면 된다.
그 code 내의 identifier들이 namespace로 묶이는 느낌이다.
ex) int animal_dog; int animal_cat; -> namespace animal { int dog; int cat; }
물론 unnamed namespace 같은걸 처리할땐 다르게 처리하겠지만, 이런식으로 쓰이는게 대부분일듯
이 내용을 scope 개념을 확장해서 구현한거네.

보통 큰 파일을 만들때, header에 declarations, source file에 따로 definitions을 모은다고 했다.
그러므로 declaration들과 definition들은 같은 이름의 namespace에 정의되도록 해줘야한다.
ex) a.hadd() 함수의 prototype이 cal namespace내에 정의됐다면, a.cppadd() 함수 definition도 cal namespace내에 정의되도록 하면 된다.

using delarations은 그냥 name을 사용한다. 그러므로 overloaded version이 있다면 한꺼번에 같이 import된다.

Namespaces GuideLines

  • external global variables 대신 named namespace에 위치시켜라
  • static global variables 대신 unnamed namespace에 위치시켜라
    (global varaible 말고 local은? ->local은 이름 충돌 가능성이 없으니 뭐 굳이..)
  • 함수나 class의 library를 만들때 namespace내에 위치시켜라. C++ strandard library 함수도 현재 std namespace내에 위치한다.
  • using directive는 old code를 namespace를 사용하도록 변경하기위한 목적에서만 잠깐 사용해라.
  • header file 내에선 using directive를 사용하지 마라.(using declaration도 마찬가지인듯?)
    원치 않았던 namespace도 같이 include하게 되면서 예상치 못한 충돌이 발생할 수 있다.
    using을 사용해버리면 해당 header에선 충돌 안할지라도, 각 header 여러개를 include하다가 충돌해버릴 수 있음.
    첫번째 답변, 두번째/세번째 답변
  • scope-resolution operator나 using declaration의 사용을 선호하라.
  • using declaration을 사용할때 global scope보단 local scope를 선호하라.

namespace는 결국 큰 project를 간단하게 하기 위한 것이다. 한 파일짜리 프로그램이면 안써도 뭐

원래 math.h(C)라던가 iostream.h(C++)은 namespace 안 사용했었는데,
cmath나 iostream같은 namespace 사용하는 버전을 만들었다.

Chapter Review

이름이 같은 두 함수를 두 파일 각각에서 사용하고싶을때, 만약 두 함수의 return type만 다르고 signature가 같다면 overloading은 안되니까, 그냥 두 함수를 static(혹은 unnamed namespace)으로 해서 각 파일에서 define하면 된다.


Programming Exercises

string class object는 empty string인지 확인할때 ""로 할 수 있음.


영단어

latitude 자유, 위도
reluctant 싫어하는
deprecate 강력히 반대하다
hint 암시, 전조
ephemeral 수명이 짧은
surmise 추측하다
tailor 맞추다, 조정하다
expediency 편법

0개의 댓글