레이블이 linkage인 게시물을 표시합니다. 모든 게시물 표시
레이블이 linkage인 게시물을 표시합니다. 모든 게시물 표시

2017-08-13

c++ static

 C++을 처음 배울 때 사람들이 많이 헷갈리는 것 중 하나가 static이다. 사실 이건 배우는 사람의 문제도 아니고 가르치는 방법의 문제도 아니다. 그저 C++에서 static을 다양한 용도로 사용하고 있기 때문이다.

static storage

 static 키워드를 사용하는 한 가지 목적은 지역 변수에 할당할 객체를 static storage로 만들기 위해서다. C++에서 block scope에 선언되는 변수가 가지는 객체는 기본적으로 automatic storage에 선언되어 block이 끝나면 자동으로 소멸한다. 하지만 간혹 특정한 범위 내에서만 접근할 수 있으면서 프로그램이 끝날 때까지 살아있는 객체, 다시 말해 지칭하는 변수는 block scope에 선언됐지만, static storage duration을 가지는 객체가 필요할 때가 있다. 이럴 때 static 한정자를 사용하면 된다. block scope에 선언되는 지역 변수에 static 한정자를 붙이면 이 변수가 가리키는 객체는 static storage duration을 가지고, block이 종료돼도 소멸하지 않는다.

internal linkage

 static 한정자는 namespace scope에 선언되는 변수에도 사용될 수 있다. 하지만 namespace scope에 선언된 변수가 가지는 객체는 기본적으로 static storage에 할당된다. 따라서 namespace scope에 선언된 변수에 붙은 static 한정자는 객체가 할당될 storage에 영향을 주지 않는다. 여기서 static은 다른 의미를 가진다.
 static 한정자는 namespace scope에서는 변수뿐 아니라 함수 선언에도 붙일 수 있다. 이 경우 선언된 변수나 함수의 linkage를 internal linkage로 바꿔서 translation unit 밖으로 나가지 않도록 해준다.

static member

 C++에서 static이 가지는 마지막 의미는 클래스의 멤버 변수나 함수를 전역 멤버 변수와 전역 함수로 만드는 것이다.
 클래스의 전역 멤버 변수는 프로그램이 종료될 때까지 살아있는 객체로, static storage에 할당된다. 전역 멤버 변수는 클래스와 독립적인 생명 주기를 가지기 때문에 전역 멤버 변수가 될 타입은 선언하는 시점에서 complete type일 필요가 없다. 전방 선언된 타입을 이용해 선언하더라도 정의하는 시점에서 complete type을 알기만 하면 된다.
 전역 멤버 함수는 클래스와는 관련 있지만, 객체와 연관되지 않는 함수를 말한다. 즉, 자기 자신(this)을 포인터로 넘겨받지 않으며, virtual function이 되거나 함수의 선언에 const를 붙일 수도 없다.
 또한, 전역 멤버 변수나 전역 멤버 함수는 그 클래스와 같은 linkage를 가진다. 즉, block scope에 선언된 클래스의 전역 함수나 전역 변수는 linkage를 가지지 않고, unnamed namespace 안에 선언된 클래스의 전역 함수나 전역 변수는 internal linkage를 가진다.

 static storage, internal linkage, static member 이 세 가지가 C++에서 static 키워드가 가지는 의미이다. 사실 이 3가지는 완전히 독립된 개념인데 같은 키워드를 사용하였기에 사람들을 헷갈리게 한다.
 C++ 뿐 아니라 다른 언어에서도 이런 경우가 종종 있다. 왜냐하면, 새 키워드를 추가하면 추가된 키워드를 다른 이름으로 사용하던 기존의 코드가 돌아가지 못하게 되고, 이는 하위 호환성을 잃는 결과를 가져오기 때문에 보통 언어에서 새 키워드를 추가하는 것을 싫어하기 때문이다.
 사실 C++에는 이미 unnamed namespace를 사용해서 이름을 internal linkage로 만드는 방법이 있다. 그래서 C++11이 제정될 때 namespace scope에 선언된 이름을 internal linkage로 만들기 위해서 static 한정자를 사용하는 것을 deprecate 시키자는 의견도 있었으나 하위 호환성을 깨지 않는 방향으로 가기 위해 받아들여지지 않았다. 결국, 이 3가지 기능이 전부 남게 되었고, 앞으로도 한동안은 이 중 어느 한 기능도 없어지지 않을 것으로 보인다.

2017-08-11

[C++] internal linkage와 external linkage의 차이

 보통 프로그램은 하나의 소스가 아닌 여러 개의 소스로 구성된다. 이 소스를 C++ 표준에서는 translation unit이라고 하고, 소스를 컴파일하는 과정을 translation이라고 한다. translation의 마지막 단계는 link인데, 이 과정을 제외하고는 모든 translation은 독립적으로 진행된다. 이때 translation unit 내에서 이름이 어느 범위까지 사용될 수 있는가를 정하는 것이 지난번 글에서 설명한 스코프이다.
 이번에 설명할 linkage는 같은 이름이 같은 entity를 지칭하는 범위를 정하는 것이다. 각 이름은 어떤 linkage를 가지는가에 따라서 translation의 마지막 단계에서 어떻게 link 될지 달라진다.
 linkage에는 internal linkage와 external linkage 두 가지 종류의 linkage가 있다. 하지만 모든 이름이 linkage를 가지는 것은 아니다. 우선은 linkage를 가지지 않는 이름에 대해 알아보도록 하겠다.

no linkage

 영어로는 has no linkage라고 하여 has internal/external linkage와 자연스럽게 대구가 된다. 하지만 한국어로 말하면 no linkage를 가진다고 하는 것은 어색하므로 그냥 linkage를 가지지 않는다고 하는 것이 적당해 보인다.
 어찌 됐든 모든 이름이 linkage를 가지는 것은 아니다. block scope에 선언된 이름은 함수와 extern으로 선언된 변수를 제외하면 전부 linkage를 가지지 않는다. 따라서 함수 안에서 선언한 로컬 클래스, enumeration, enumerator, 타입 등은 전부 linkage를 가지지 않는다.
 linkage를 가지지 않는 이름은 선언된 스코프에서밖에 접근할 수 없고, 스코프 밖에서 그 이름을 다시 사용해도 그건 다른 entity를 지칭하는 것이 된다. static 한정자를 사용하여 static storage에 저장한 경우도 마찬가지다. 이는 block scope에 선언된 이름은 함수 안에서만 사용하기 위한 것이기 때문이다.

internal linkage

 internal linkage를 가지는 이름은 외부 translation unit으로 공개되지 않는다. internal linkage를 가지는 이름은 다음과 같다.

  1. namespace scope에 선언된 static specifier가 붙은 이름
  2. namespace scope에 선언된 anonymous union의 데이터 멤버
  3. namespace scope에 선언된 const 혹은 constexpr이 붙고 volatile은 아닌 변수
  4. unnamed namespace에 선언된 모든 이름

 internal linkage를 가지는 이름은 두 개 이상의 translation unit에서 공유할 수 없다. 따라서 다른 translation unit에서 같은 스코프에 같은 이름으로 선언된 변수나 함수가 있어도 물리적으로 이는 다른 객체를 가리킨다. 따라서 헤더 파일에 internal linkage를 가지는 mutable 한 객체를 선언한 경우 읽는 사람이 코드를 잘못 읽을 여지를 줄 수 있기에 namespace scope에 선언되는 mutable 한 객체는 헤더 파일에 선언하지 않는 것이 좋다. 만약 반드시 헤더 파일에 선언해야 한다면, external linkage를 가지도록 해야 한다.

external linkage

 위에서 설명한 internal linkage와 다르게 external linkage를 가지는 이름은 다른 translation unit과 공유된다. 다시 말해서 어떤 이름이 external linkage를 가진다면 그 이름은 서로 다른 translation unit에서 사용되더라도 같은 내용을 가져야 한다.

  1. namespace scoep에 선언 된 이름 중 internal linkage가 아닌 이름.
  2. block scope에 선언된 함수
  3. block scope에 선언된 변수 중에서 extern으로 선언된 변수

external linkage를 가지는 이름은 위와 같다. 만약 external linkage로 선언된 이름이 함수일 경우 이 함수는 다른 translation unit에서 같은 코드 영역을 가리키게 되며, 그 함수에서 선언된 static storage나 thread_local storage에 저장되는 지역 변수도 공유하게 된다. 만약 그 이름이 객체를 지칭하는 변수라면, 다른 translation unit에서 사용될 때 같은 메모리를 여역을 공유하며, 따라서 한 translation unit에서 객체의 값을 수정하면 다른 translation unit에서도 수정된 값을 보게 된다. 또한, external linkage로 선언된 클래스가 있다면 그 클래스의 멤버 함수들과 전역 멤버 변수들도 전부 external linkage를 가지게 된다.