c++에서 사용되는 키워드에 대해서 간단히 정리합니다. 

Static

 static 키워드는 global scope, namespace scope, class scope, local scope에서 선언할 수 있으며

 상황에 따라서 다양한 용도로 사용 되어 집니다.

static 링킹

 기본적으로 전역 변수는 프로그램 시작할 때 할당되고 프로그램이 종료될 때 할당 해제되며

 externale linkage를 가집니다. 

 전역 변수에 static 키워드를 붙이면 internale linkage를 가지도록 지정합니다.

클래스 안에서 static 키워드

 클래스 안에서 static 키워드는 선언된 대상을 클래스의 객체가 아닌 클래스 단위로 종속시키는 역할을 합니다. 

 클래스 안에서 static 데이터 멤버나 메서드는 클래스 객체가 존재하지 않아도 사용할 수 있습니다. 

// m_StaticCount를 static 객체로 선언합니다.  
class StaticObject
{
public :
    static int m_StaticCount;
};
int StaticObject::m_StaticCount = 0;

int _tmain(int argc, _TCHAR* argv[])
{
    std::cout << "StaticObject::m_StaticCount = " << StaticObject::m_StaticCount << endl;
    
    // s1 객체를 선언하고 m_StaticCount를 증가시킵니다.
    StaticObject s1;
    ++s1.m_StaticCount;
    std::cout << "s1.m_StaticCount = " << s1.m_StaticCount << endl;

    // s2 객체를 선언하고 m_StaticCount를 증가시킵니다.
    StaticObject s2;
    ++s2.m_StaticCount;
    std::cout << "s2.m_StaticCount = " << s2.m_StaticCount << endl;
   
   // m_StaticCount는 객체와 관계없이 값을 유지합니다. 
    std::cout << "StaticObject::m_StaticCount = " << StaticObject::m_StaticCount << endl;

    return 0;
}

예제의 결과 내용

 예제를 보면 m_StaticCount라는 변수는 객체에 종속되지 않고 클래스에서 공통으로 사용되는 것을

 확인할 수 있습니다.

함수 내에서 static 키워드

 함수 내에서 static 키워드를 사용하면 함수의 리턴 이후에도 유지되는 static 로컬 변수를 지정합니다. 

void TaskProcess()
{
    // 함수가 호출되는 처음에만 초기화 합니다. 
    static bool s_InitCheck = false;

    // 처음 호출이라면 초기화 작업을 진행합니다.
    if (!s_InitCheck)
    {
        // 초기화 작업 진행 
        cout << "TaskProcess Init s_InitCheck = " << boolalpha << s_InitCheck << endl;
        s_InitCheck = true;
    }

    // 이후 작업 진행 
    
    cout << "TaskProcess() s_InitCheck = " << boolalpha << s_InitCheck << endl;
}

int _tmain(int argc, _TCHAR* argv[])
{
    TaskProcess();
    TaskProcess();

    return 0;
}

결과 내용

 예제를 보시면 s_InitCheck는 TaskProcess()가 처음 호출될 때만 초기화 구문이 동작합니다. 

 이후에 TaskProcess()를 호출하면 이전의 값이 계속 유지됩니다.

 s_InitCheck객체의 생명주기는 첫 TaskProcess() 함수가 호출될 때 생성되고 프로그램이 종료될 때 해제됩니다. 

 (일반적으로 함수 내의 Static 변수는 코드 관리가 힘들기 때문에 권장하지 않습니다. 

 객체를 통한 관리로 전환해야 합니다.) 

static 클래스 객체와 전역 변수 초기화 순서

class StaticObject
{
public:
    static Person m_StaticPerson;
};
// 전역변수 s_globalCount와 StaticObject::m_StaticPerson는 프로세스가 생성될 때 
// 할당됩니다. 
Person StaticObject::m_StaticPerson("m_StaticPerson", 100);
Person s_globalCount("s_globalCount", 100);

 예제에서 전역 변수 s_globalCount와 StaticObject::m_StaticPerson는 main(진입점 함수)가 수행되기 전에 초기화를 

 진행합니다. 위의 예제는 하나의 파일에서 선언되어 있기 때문에 StaticObject::m_StaticPerson가 먼저 초기화됩니다. 

 하지만 여러 소스에 선언된 전역 변수와 static 클래스 객체 변수들의 초기화 순서는 정해져 있지 않기 때문에

 전역 변수들이 서로의 초기화에 연관이 있다면 문제가 발생됩니다. 

 전역 변수를 사용하는 것 자체도 권장되지 않습니다. 

extern

extern 키워드는 명시적으로 external 링킹 할 대상으로 지정합니다. 

const

 const 키워드는 constant의 축약으로 절대 변경되지 말 것을 지정합니다. 

 만약 const 가 지정된 변수를 수정하려고 하면 컴파일러에 의해서 에러를 검출할 수 있습니다. 

 const를 지정하면 컴파일러가 코드를 최적화할 때 대상이 수정되지 않는다는 것을 인지하여

 효율적인 코드를 생성할 수  있습니다.

 데이터를 선언할 때 const를 사용할 수 있으면 무조건 const를 붙이는 것이 좋습니다.

const 변수와 const 파라미터

 변경되지 말아야 할 데이터 변수나 파라미터가 있다면 const 키워드를 통해서 보호할 수 있습니다.

 또한 상수값 사용 시에 #define을 대신할 수 있습니다. (c++ 11부터는 constexpr이 추가됨)

void f(const DWORD constData)
{
    // error C3892 constData 수정 불가
    constData++;
}

int _tmain(int argc, _TCHAR* argv[])
{
    const DWORD kConstValue = 100;
    // error C3892 kConstValue 수정 불가
    kConstValue = 101;    
    f(200);

    return 0;
}

 예제를 보면 const로 선언된 변수에 대해서는 수정을 시도하면 error가 발생되는 것을 확인할 수 있습니다.

 포인터에 const를 사용하면 어떻게 될까요?

    DWORD* pd = nullptr;
    pd = new DWORD[10];
    if (pd)
        pd[4] = 10;

const가 없을 때는 정상적으로 동작하는 예제입니다. 

    const DWORD * pd = nullptr;
    // DWORD const * pd = nullptr; 도 가능

    pd = new DWORD[10]; 

    if (pd)
        pd[4] = 10; // error 발생 C3892 pd[4] 를 수정할 수 없음

pd변수에서 *의 왼쪽에 const를 지정하면 할당된 pd가 가리키는 데이터에 대해서 수정할 수 없습니다.

    DWORD * const pd = nullptr;
    pd = new DWORD[10]; // error 발생 C3892 pd를 수정할 수 없음
    
    // DWORD * const pd = new DWORD[10]; 형태는 가능합니다. 

pd변수에서 *의 오른쪽에 const를 지정하면 한번 초기화된 pd 포인터를 수정할 수 없습니다.

    const DWORD* const pd = nullptr;
    pd = new DWORD[10]; // error 발생 C3892 pd를 수정할 수 없음

    if (pd)
        pd[4] = 10; // error 발생 C3892 pd[4] 를 수정할 수 없음

둘 다 const로 지정할 수도 있습니다. 

이 처럼 포인터의 경우 const의 위치에 따라서 동작이 달라지기 때문에 주의 해야 합니다.

const 메서드

 클래스 내부 메서드를 const로 선언할 수 있습니다. 

class CUser
{
public :
    void setId(DWORD setId )
    { m_Id = setId; }

    // 멤버 함수를 const로 지정합니다. 
    // 해당 함수 내에서는 값을 변경 할 수 없습니다.
    DWORD getId() const 
    { return m_Id; }
    
private:
    DWORD m_Id;
};

 예제의 CUser의 클래스의 getId 메서드에 const를 지정하였습니다. const가 지정된 클래스 메서드에서는 

 mutable로 선언된 것을 제외하고는 값을 변경할 수 없습니다. 

    const CUser user;
    user.getId();
    // error 발생 c2662 const로 선언된 객체는 const 함수만 호출 할 수 있습니다.
    user.setId(10);

 const CUser 객체를 생성해서 메서드를 호출해보면 const로 지정된 함수들만 호출할 수 있습니다.

 const 멤버 메서드를 통해서 const 객체의 안전성을 보장합니다.

mutable

 mutable 키워드는 non-static이면서 non-const인 클래스의 데이터 멤버에만 적용할 수 있습니다.

 데이터 멤버가 mutable로 지정되면 클래스의 const 멤버 함수에서 해당 데이터 멤버를 수정할 수 있습니다.

class CUser
{
public :
    void setId(DWORD setId )
    { 
        ++CallFuncCount;
        m_Id = setId; 
    }

    DWORD getId() const 
    { 
        ++CallFuncCount;
        return m_Id;
    }
    
private:
    mutable DWORD CallFuncCount;
    DWORD m_Id;
};

 예제를 보면 getId() 멤버 함수는 const로 지정되어 있지만 CallFuncCount는 mutable로 지정되었기 때문에 

 getId() 함수 내에서 수정이 가능합니다. 

volatile

 volatile의 뜻은 휘발성입니다. 이 키워드로 선언된 변수는 외부적인 요인으로 그 값이 언제든지 바뀔 수 있기 때문에

 최적화를 사용하지 않습니다.

 시스템은 요청 때마다 매번 volatile의 현재의 값을 읽어와서 수행하고 쓰기는 할당 즉시 기록됩니다. 

 visual studio에서는 volatile 키워드를 cpu의 설계에 따라서 다르게 해석합니다. 

 ARM은 기본으로 /volatile:iso가 지정된 것처럼 수행합니다.

 ARM이 아닌 경우는 기본으로 /volatile:ms가 지정된 것처럼 수행됩니다. 

 ARM이 아닌 경우에는 명시적으로 /volatile:iso를 지정하는 것이 좋습니다. 

/volatile:iso

 c++ 11 ISO 표준 코드 안에서 volatile 키워드는 오직 하드웨어 접근에만 사용합니다.

 스레드 간의 통신에 사용하면 안 됩니다. ( std::atomic 같은 스레드 동기화 객체를 사용하셔야 합니다.)

/volatile:ms

 micro soft의 volatile은 다중 스레드에서 안전하도록 메모리 접근에 대한 lock과 release가 동작하도록 구현되어

 있습니다. 하지만 표준이 아니기 때문에 다른 플랫폼에 이식할 때 문제가 발생할 수 있습니다. 

+ Recent posts