반응형
 

스토리지 클래스(C++)

스토리지 클래스(C++)Storage classes (C++) 이 문서의 내용 --> A 저장소 클래스가 컨텍스트에서 C++ 변수를 선언 하는 개체의 수명, 링크 및 메모리 위치를 제어 하는 형식 지정자입니다.A storage class in the context of C++ variable declarations is a type specifier that governs the lifetime, linkage, and memory location of

docs.microsoft.com

 

C++ 변수 선언 구문에서 저장소 클래스는 개체의 수명, 연결 및 메모리의 위치를 제어하는 형식 지정자입니다. 개체는 단 한 가지의 저장소 클래스를 사용할 수 있습니다. 만약 extern, static, thread_local 지정자를 사용하지 않는다면, 변수는 자동 저장소를 가진 블록 내로 정의될 수 있습니다. 자동 개체 및 변수는 연결(Linkage)이 없습니다. 블록 외부에서 코드를 볼 수 없습니다.

 

 

참고

  1. mutable 키워드는 저장소 클래스로 정의될 수 있습니다. 그러나 클래스 정의의 멤버 목록에서만 사용할 수 있습니다.
  2. Visual Studio 2010 과 이후 버전: auto 키워드는 더이상 C++의 스토리지 클래스가 아니며 register 키워드는 사용되 않습니다. Visual Studio 2017 version 15.7 과 이후 버전: (/std:c++17을 사용함): register 키워드는 C++언어에서 제거되었습니다.

 

register 경고

 

/std:c++17 설정

 

register 자체예제_

1
2
3
4
5
6
7
int main()
{
    register int val = 0;
 
    val = 1;
    return 0;
}
 

 

 

안 된다는 글귀가 쓰여있어서 한번 실험을 해보았는데 디버깅과 실행이 잘 된다. 다만 위에 사진에서 보았듯이 해당 명령을 깔아놓은 상태에서 C5033의 경고를 띄운다. 자세한 register 키워드는 어떻게 처리되는 지는 아래에서 다룰 생각이다.

 

 

 

 

 

static

static 키워드는 로컬, 전역, namespace, 클래스 범위 내 함수와 변수에 선언될 수 있습니다.

 

static(정적) 유효기간이란, 개체나 변수가 프로그램의 시작 부분에서 할당부터, 종료 부분에서 해제까지를 뜻합니다.

External linkage(외부연결) 이란, 해당 변수가 속해있는 파일 바깥에서 해당 변수의 이름을 볼 수 있는 것을 의미합니다.

Internal linkage(내부연결) 이란, 해당 변수가 속해있는 파일 바깥에서 해당 변수의 이름을 볼 수 없는 것을 의미합니다.

 

기본적으로, 전역 namespace에 정의된 변수와 개체는 정적유효기간과 외부 연결을 가집니다.

static 키워드는 아래와 같은 상황에 사용될 수 있습니다.

 

  1. 파일 범위(전역 혹은 namespaece 범위)에서 변수나 함수의 선언을 할 경우, static 키워드는 해당 변수, 함수가 내부연결이 되도록 지정합니다. 변수 선언 시, 정적유효기간 내이고 그 값을 지정하지 않는 경우에는 컴파일러는 0으로 초기화합니다.
  2. 함수내에서 변수 선언 시, static 키워드는 변수가 해당 함수를 호출하니는 사이에 상태를 유지하도록 지정합니다.
  3. 클래스 선언 중 멤버 데이터를 선언할 때static 키워드는 멤버의 복사 하나가 클래스의 모든 할당(인스턴스)에 공유되도록 지정합니다. static 데이터 멤버는 반드시 파일 범위에 정의되어야 합니다. const static 으로 선언하는 필수 데이터(integral data)에는 초기화(initailizer)가 있을 수 있습니다.
  4. 클래스 선언 중 멤버 함수를 선언할 때, static 키워드는 해당 함수가 모든 할당(인스턴스)에 공유되도록 지정합니다. static 멤버 함수는 (그냥) 인스턴스 멤버 변수를 엑세스(구동 및 조작)할 수 없습니다. 해당 함수는 암시적인 this 포인터를 가지지 않기 때문입니다. 멤버를 엑세스 하기 위해서는, 포인터나 참조형을 가진 매개 변수를 가진 함수의 형태로 선언되어야 합니다.
  5. union을 static으로 선언할 수 없습니다. 하지만, 전역으로 선언된 익명 union은 반드시 명시적으로 static 선언이 되어야 합니다.

 

이 예에서는 함수에서 static을 선언한 변수가 해당 함수를 호출하는 사이에 상태를 유지하는 방법을 보여줍니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// compile with: /EHsc
#include <iostream>
 
using namespace std;
void showstat( int curr ) {
   static int nStatic;    // Value of nStatic is retained
                          // between each function call
   nStatic += curr;
   cout << "nStatic is " << nStatic << endl;
}
 
int main() {
   for ( int i = 0; i < 5; i++ )
      showstat( i );
}
 
 

 

실행결과

1
2
3
4
5
nStatic is 0
nStatic is 1
nStatic is 3
nStatic is 6
nStatic is 10
 

위 예제에서는 nStatic이 매 호출마다 선언되는 것이 아니라, 해당 변수가 유지되어 계속 값을 누적시키는 것을 단편적으로 보여주는 예제이다.

 

아래 예제는 클래스 내 static 의 사용법을 보여주는 예제이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// compile with: /EHsc
#include <iostream>
 
using namespace std;
class CMyClass {
public:
   static int m_i;
};
 
int CMyClass::m_i = 0;
CMyClass myObject1;
CMyClass myObject2;
 
int main() {
   cout << myObject1.m_i << endl;
   cout << myObject2.m_i << endl;
 
   myObject1.m_i = 1;
   cout << myObject1.m_i << endl;
   cout << myObject2.m_i << endl;
 
   myObject2.m_i = 2;
   cout << myObject1.m_i << endl;
   cout << myObject2.m_i << endl;
 
   CMyClass::m_i = 3;
   cout << myObject1.m_i << endl;
   cout << myObject2.m_i << endl;
}
 
 

 

실행결과

1
2
3
4
5
6
7
8
0
0
1
1
2
2
3
3
 

 

위 3번에 해당하는 내용이다. 하나의 클래스에서 할당된 인스턴스 두 개가 m_i라는 staitc 변수 하나를 공유하는 모습이다.

 

 

 

 

아래 예제는 멤버 변수 내 static으로 선언된 지역변수(Local variable)를 보여줍니다. static 변수는 전체 프로그램에서 사용될 수 있습니다. 모든 같은 형식의 인스턴스들은 정적변수(static)의 동일한 사본을 공유합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// compile with: /EHsc
#include <iostream>
using namespace std;
struct C {
   void Test(int value) {
      static int var = 0;
      if (var == value)
         cout << "var == value" << endl;
      else
         cout << "var != value" << endl;
 
      var = value;
   }
};
 
int main() {
   C c1;
   C c2;
   c1.Test(100);
   c2.Test(100);
}
 
 

 

실행결과

1
2
var != value
var == value
 

 

C++11 에서 실행시, 정적 로컬 변수 초기화는 스레드로부터 안전합니다. 이 특징은 때로는 마법의 정적함(Magic Static)이라 부릅니다ㅋㅋㅋ;;. 하지만, 다중 스레드 응용프로그램 환경에서는 모든 후속 할당은 동기화되어야 합니다. 해당 static 정적-안전-스레드 초기화는  /Zc:threadSafeInit- 명령을 사용하여 CRT에 종속되지 않도록 할 수 있습니다.

 

 

이 마지막 구문은 당최 무슨 소리인지 모를 수 있겠다... 특히 MSDN에 어울리지 않은(순전히 본인 생각ㅇㅇ) MAGIC이란 단어를 쓰면서 표현한 바로 그 현상이기 때문이다.

 

해당 경우는 구문에도 서술되어 있듯이 스레드 환경일 때 빚어지는 현상이다. 만약 스레드로... 그것도 특히나 static으로 선언을 하게 되면 변수의 값이 [공유]된다는 측면에서 스레드에 치명적인 오류를 안길 것만 같기도 하다. 예를 들어, 어떠한 싱글톤 클래스를 만들고 스레드 A를 그 싱글톤 클래스를 호출하는 작업을 무작정 반복한다 했을 때, 첫번째 반복과 두번째 반복시 과연 인스턴스가 한 번만 생성된다는 것이다.

 

말이 좀 어렵게 된 것 같아 다시 설명하겠다. 최초 한 번은 싱글톤이 실행되어 인스턴스화 되는 것이 맞다. 하지만 두 번째 이후 실행 시 아직, 해당 객체가 인스턴스화 되지 않았다면 또다시 인스턴스화 시키려 할 것이다. 이를 단순히 nullptr(할당이 됐는가에 대한 여부)로만 따지기엔 취약점이 뚜렷하다는 점이다. 이것은 또한 단순하게 Lock을 걸어 처리할 수 있다.

Scott-Meyers-Cpp-and-the-Perils-of-Double-Checked-Locking.pdf
0.15MB

참고 자료_
https://blog.mbedded.ninja/images/2017/08/Scott-Meyers-Cpp-and-the-Perils-of-Double-Checked-Locking.pdf

 

 

 

 

여담_ static 지정자로 선언한 변수는 어셈블리로 보면 0이나 정의를 하지 않았을 시 BSS Segment로, 값을 부여하면 DATA Segment로 할당된다. 이 영역에 궁금한 점은 아래 포스팅을 참고하기 바란다.

 

메모리

메모리란 무엇인가 해당 절은 그 참고자료를 나름대로 해석을 한 것이므로 정보의 신뢰성(무료라이센스이지만 출처가 조금 애매함)을 따진다면 완전히 신뢰하지는 않았으면 한다. 다만 키워드에 중점을 두는 식으..

mtding00.tistory.com

 

extern

extern으로 선언된 객체와 변수는 다른 변동 단위(translation unit) 또는 외부 연결을 가진 둘러싼 범위에 대해 객체를 선언합니다.

extern 저장클래스와 함께 선언된 const 변수는 외부연결이 강제됩니다. extern const 변수의 초기화는 정의된 변역 단위(translation unit)에 허가됩니다. 정의된 변역 단위가 아닌 변역 단위의 초기화는 정의되지 않은 결과를 생성합니다. 더 많은 정보를 원하면, Using extern to Specify Linkage를 참고하세요.

 

 

 

변역 단위 - translation unit

 

Translation unit (programming) - Wikipedia

In C and C++ programming language terminology, a translation unit is the ultimate input to a C or C++ compiler from which an object file is generated.[1] In casual usage it is sometimes referred to as a compilation unit. A translation unit roughly consists

en.wikipedia.org

더보기

Translation unit (programming)

From Wikipedia, the free encyclopedia

In C and C++ programming language terminology, a translation unit is the ultimate input to a C or C++ compiler from which an object file is generated.[1] In casual usage it is sometimes referred to as a compilation unit. A translation unit roughly consists of a source file after it has been processed by the C preprocessor, meaning that header files listed in #include directives are literally included, sections of code within #ifndef may be included, and macros have been expanded.

Context[edit]

A C program consists of units called source files (or preprocessing files), which, in addition to source code, includes directives for the C preprocessor. A translation unit is the output of the C preprocessor – a source file after it has been preprocessed.

Preprocessing notably consists of expanding a source file to recursively replace all #include directives with the literal file declared in the directive (usually header files, but possibly other source files); the result of this step is a preprocessing translation unit. Further steps include macro expansion of #define directives, and conditional compilation of #ifdef directives, among others; this translates the preprocessing translation unit into a translation unit. From a translation unit, the compiler generates an object file, which can be further processed and linked (possibly with other object files) to form an executable program.

Note that the preprocessor is in principle language agnostic, and is a lexical preprocessor, working at the lexical analysis level – it does not do parsing, and thus is unable to do any processing specific to C syntax. The input to the compiler is the translation unit, and thus it does not see any preprocessor directives, which have all been processed before compiling starts. While a given translation unit is fundamentally based on a file, the actual source code fed into the compiler may appear substantially different than the source file that the programmer views, particularly due to the recursive inclusion of headers.

Scope[edit]

Translation units define a scope, roughly file scope, and functioning similarly to module scope; in C terminology this is referred to as internal linkage, which is one of the two forms of linkage in C. Names (functions and variables) declared outside of a function block may be visible either only within a given translation unit, in which case they are said to have internal linkage – they are not visible to the linker – or may be visible to other object files, in which case they are said to have external linkage, and are visible to the linker.

C does not have a notion of modules. However, separate object files (and hence also the translation units used to produce object files) function similarly to separate modules, and if a source file does not include other source files, internal linkage (translation unit scope) may be thought of as "file scope, including all header files".

Code organization[edit]

The bulk of a project's code is typically held in files with a .c suffix (or .cpp, .c++ or .cc for C++, of which .cpp is used most conventionally). Files intended to be included typically have a .h suffix ( .hpp or .hh are also used for C++, but .h is the most common even for C++), and generally do not contain function or variable definitions to avoid name conflicts when headers are included in multiple source files, as is often the case. Header files can be, and often are, included in other header files. It is standard practice for all .c files in a project to include at least one .h file.

See also[edit]

Single Compilation Unit

 

c나 cpp와 같은 전처리를 거친 코드 단위을 뜻한다. 매크로나 헤더 추가와 같은 것들을 모두 끝마친 상태를 말하는 것이며, extern은 말 그대로 모든 코드가 정리된 이후 선언되는 일종의 최종적인 선언과도 같은 것이다. 그러니 외부에 존재할 수 있는 것이며, 이후에 접근하겠다는 개념이 성립하는 것이다.

 

 

 

 

아래는 해당 키워드의 컴파일러옵션을 설명한다. 아직 이런 옵션은 달아본적이 없다만 충분히 버젼업에 대한 오류나 경고가 발생할 여지가 있으므로 꼭 체크를 해야하긴 할 부분이다.

 

/Zc:externConstexpr 컴파일러 옵션을 사용하면, extern constexpr를 사용하여 선언한 변수에 외부 연결을 적용합니다. 초기 Visual Studio 버젼과 기본값 또는 /Zc:externConstexpr-  으로 지정된 경우, Visual Studio는 extern 키워드를 사용하여도 constexpr 변수에 내부연결을 적용시킵니다. /Zc:externConstexpr 옵션은 Visual Studio 2017 Update 15.6. 에 사용할 수 있게 되었고, 기본값에서 제외되었습니다. /permissive- 옵션은 /Zc:externConstexpr 옵션을 활성화 하지 않습니다.

 

 

아래 코드는 두개의 extern 선언 DefinedElsewhere(다른 번역 단위에 정의된 이름을 참조함)과 DefinedHere(둘러싼 범위에 정의된 이름을 참조함)을 보여줍니다. 

 

1
2
3
4
5
6
7
8
9
10
// DefinedElsewhere is defined in another translation unit
extern int DefinedElsewhere;
int main() {
   int DefinedHere;
   {
      // refers to DefinedHere in the enclosing scope
      extern int DefinedHere;
   }
}
 
 

 

 

 

thread_local (C++11)

thread_local로 정의된 변수는 생성된 thread에서만 엑세스될 수 있습니다. 해당 변수는 thread가 생성될 때 생성되고, thread가 파괴될 때 파괴됩니다. 각 thread는 고유한 변수의 복사값을 가집니다. 윈도우 운영체제에서, thread_local은 기능적으로 Microsoft-지정자 __delclspec(thread) 항목과 기능적으로 같은 활동입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
thread_local float f = 42.0// Global namespace. Not implicitly static.
 
struct S // cannot be applied to type definition
{
    thread_local int i; // Illegal. The member must be static.
    thread_local static char buf[10]; // OK
};
 
void DoSomething()
{
    // Apply thread_local to a local variable.
    // Implicitly "thread_local static S my_struct".
    thread_local S my_struct;
}
 
 

thread_local 지정자에 대한 주의사항:

  • 동적으로 초기화된 DLL 내 thread_ocal 변수는 호출된 모든 thread에서 정확하게 초기화되지 않을 수 있습니다. 더 많은 정보를 원하면 thread를 참고하세요.
  • thread_local 지정자는 static 혹은 extern 과 함께 결합될 수 있습니다.
  • thread_local은 오로지 데이터의 선언과 정의만 할 수 있습니다. thread_local은 함수의 정의와 선언에 사용할 수 없습니다.
  • thread_local을 static의 저장기간의 데이터들에 대해서만 지정할 수 있습니다. 이는 전역 데이터 객체(static 과 extern을 포함한), 지역 static 객체, 클래스의 static 멤버들을 말합니다. thread_local로 선언 된 지역 변수는 다른 저장 클래스가 제공되지 않으면 암시 적으로 정적입니다. 즉, 블록 범위 thread_local에서 thread_local static과 동일합니다.
  • 선언 및 정의가 동일한 파일 또는 개별 파일에서 발생하는지 여부에 관계없이 thread 로컬 객체의 선언 및 정의에 대해 thread_local을 지정해야합니다.

Windows환경에서 thread_local은 __declspec(thread)을 유형 정의에 적용 할 수 있고 C 코드에서 유효하다는 점을 제외하면 __declspec(thread)와 기능적으로 동일합니다. 가능하면 thread_local을 사용하세요, C ++ 표준의 일부이므로 더 이식성이 뛰어나기 때문입니다.

 

 

이 키워드는 써보고 나니... 쓸까? ㅋㅋㅋ 그래도 일단 정확하게 했고 해당 키워드는 구현이나 아는 바가 없어서 궁금한 점이 없다 ㅠㅠ... 만약 더 많은 코드를 쓰게 되면 해당 부문이 사라지고 예제가 들어찰 것이다.

 

 

 

 

register

Visual Studio 2010 과 이후 버전: auto 키워드는 더이상 C++의 스토리지 클래스가 아니며 register 키워드는 사용되 않습니다. Visual Studio 2017 version 15.7 과 이후 버전: (/std:c++17을 사용함): register 키워드는 C++언어에서 제거되었습니다.

1
register int val; // warning C5033: 'register' is no longer a supported storage class
 

 

 

 

 

 

예제: 자동 vs 정적 초기화 (static initialization)

로컬 자동 객체 또는 변수는 정의에 도달 할 때마다 초기화됩니다.

로컬 정적 객체 또는 변수는 처음 정의 될 때 초기화됩니다.

 

 

I1, I2, I3의 세 개의 객체가 초기화, 소멸 그리고 정의가 찍힌 로그를 가진 클래스의 정의를 아래 예제에서 참고하세요.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
// compile with: /EHsc
#include <iostream>
#include <string.h>
using namespace std;
 
// Define a class that logs initializations and destructions.
class InitDemo {
public:
    InitDemo( const char *szWhat );
    ~InitDemo();
 
private:
    char *szObjName;
    size_t sizeofObjName;
};
 
// Constructor for class InitDemo
InitDemo::InitDemo( const char *szWhat ) :
    szObjName(NULL), sizeofObjName(0) {
    if ( szWhat != 0 && strlen( szWhat ) > 0 ) {
        // Allocate storage for szObjName, then copy
        // initializer szWhat into szObjName, using
        // secured CRT functions.
        sizeofObjName = strlen( szWhat ) + 1;
 
        szObjName = new char[ sizeofObjName ];
        strcpy_s( szObjName, sizeofObjName, szWhat );
 
        cout << "Initializing: " << szObjName << "\n";
    }
    else {
        szObjName = 0;
    }
}
 
// Destructor for InitDemo
InitDemo::~InitDemo() {
    if( szObjName != 0 ) {
        cout << "Destroying: " << szObjName << "\n";
        delete szObjName;
    }
}
 
// Enter main function
int main() {
    InitDemo I1( "Auto I1" ); {
        cout << "In block.\n";
        InitDemo I2( "Auto I2" );
        static InitDemo I3( "Static I3" );
    }
    cout << "Exited block.\n";
}
 
 

 

실행 결과_

1
2
3
4
5
6
7
8
Initializing: Auto I1
In block.
Initializing: Auto I2
Initializing: Static I3
Destroying: Auto I2
Exited block.
Destroying: Auto I1
Destroying: Static I3
 

 

이 예제는 오브젝트 I1, I2 및 I3이 언제 어떻게 초기화되는지, 그리고 언제 파괴되는지를 보여줍니다.

프로그램에 대해 몇 가지 주의해야 할 점이 있습니다.

 

  • 첫째, I1 과 I2는 프로그램 진행이 정의된 범위에서 벗어날 때에 자동적으로 소멸된다.
  • 둘째, C++에서는, 범위 시작 위치에 객체나 변수를 선언한 필요는 없습니다.
    그러므로, 진행이 그것들의 정의에 도달했을 때에만 해당 객체들의 초기화가 진행됩니다. (해당 예시는 I2와 I3의 정의 부분입니다.) 실행 결과에서 정확히 그들의 초기화 순서를 보여줍니다.

  • 마지막으로, I3와 같은 static 지역 변수는 프로그램이 진행되는 동안 해당 값은 유지됩니다. 그러나 프로그램이 종료(Terminate)될 경우 소멸됩니다.

 

 static으로 선언된 I3가 프로그램 종료시 해제가 되는 것이라 위에서 언급했으므로 가장 마지막에 해제된다.

반응형

'C++' 카테고리의 다른 글

Mutable, Auto  (0) 2019.12.25
volatile  (0) 2019.12.25
Timer - 시간 측정기  (0) 2019.12.25
const*, constexpr**  (0) 2019.12.25
Scope - 범위  (0) 2019.12.25
Posted by Kestone Black Box
,