C++ 핵심 키워드 완벽 가이드 | static·extern·const·constexpr·inline·volatile·mutable 심층 분석

C++ 핵심 키워드 완벽 가이드 | static·extern·const·constexpr·inline·volatile·mutable 심층 분석

이 글의 핵심

C++ 핵심 키워드들을 완벽하게 정리합니다. static, extern, const, constexpr, inline, volatile, mutable의 의미, 링키지, 스토리지 클래스, 메모리 레이아웃, 컴파일러 최적화, 실전 활용 패턴까지 깊이 있게 다룹니다.

이 글의 핵심

C++의 핵심 키워드들(static, extern, const, constexpr, inline, volatile, mutable)을 완벽하게 정리합니다. 각 키워드의 의미, 링키지, 스토리지 클래스, 메모리 레이아웃, 컴파일러 최적화, 실전 활용 패턴까지 깊이 있게 다룹니다.

실무 경험 공유: 대규모 C++ 프로젝트에서 링키지 오류, ODR 위반, 최적화 문제를 해결하면서 얻은 경험을 바탕으로, 각 키워드의 실제 동작과 올바른 사용법을 정리했습니다.


1. static 키워드

1.1 세 가지 의미의 static

C++에서 static문맥에 따라 세 가지 다른 의미를 가집니다.

1) 파일 스코프 static (내부 링키지)

// utils.cpp
static int counter = 0;  // 이 파일 내부에서만 접근 가능

static void helperFunction() {
    counter++;
}

특징:

  • 내부 링키지: 다른 파일에서 접근 불가
  • ODR 위반 방지: 같은 이름이 여러 파일에 있어도 충돌 없음
  • 현대 C++: 익명 네임스페이스 권장
// 현대 C++ 스타일
namespace {
    int counter = 0;  // static int counter = 0; 과 동일
    
    void helperFunction() {
        counter++;
    }
}

2) 클래스 static 멤버

class Database {
public:
    static int connectionCount;  // 선언
    
    static void incrementConnections() {
        connectionCount++;
    }
};

// 정의 (cpp 파일)
int Database::connectionCount = 0;

특징:

  • 모든 인스턴스가 공유: 클래스당 하나의 복사본
  • this 포인터 없음: 인스턴스 없이 호출 가능
  • static 멤버만 접근 가능: 인스턴스 멤버 접근 불가

3) 함수 내부 static (정적 지역 변수)

int getNextId() {
    static int id = 0;  // 첫 호출 시 한 번만 초기화
    return ++id;
}

int main() {
    std::cout << getNextId() << "\n";  // 1
    std::cout << getNextId() << "\n";  // 2
    std::cout << getNextId() << "\n";  // 3
}

특징:

  • 프로그램 시작 시 메모리 할당: 스택이 아닌 데이터 세그먼트
  • 첫 호출 시 초기화: 이후 호출에서는 초기화 생략
  • 스레드 안전 초기화: C++11부터 보장 (Magic Static)

1.2 static 메모리 레이아웃

int globalVar = 42;           // .data 세그먼트
static int fileVar = 100;     // .data 세그먼트 (내부 링키지)

void function() {
    static int localVar = 200; // .data 세그먼트
    int stackVar = 300;        // 스택
}

메모리 구조:

+-------------------+
| Code (.text)      | ← 함수 코드
+-------------------+
| Data (.data)      | ← globalVar, fileVar, localVar
+-------------------+
| BSS (.bss)        | ← 초기화되지 않은 static 변수
+-------------------+
| Heap              | ← 동적 할당
+-------------------+
| Stack             | ← stackVar
+-------------------+

1.3 static 활용 패턴

싱글톤 패턴 (Meyer’s Singleton)

class Logger {
public:
    static Logger& getInstance() {
        static Logger instance;  // 스레드 안전 초기화
        return instance;
    }
    
    void log(const std::string& message) {
        std::cout << message << "\n";
    }
    
private:
    Logger() = default;
    Logger(const Logger&) = delete;
    Logger& operator=(const Logger&) = delete;
};

// 사용
Logger::getInstance().log("Hello");

팩토리 레지스트리

class ShapeFactory {
public:
    using Creator = std::unique_ptr<Shape>(*)();
    
    static void registerShape(const std::string& name, Creator creator) {
        getRegistry()[name] = creator;
    }
    
    static std::unique_ptr<Shape> create(const std::string& name) {
        auto& registry = getRegistry();
        auto it = registry.find(name);
        return it != registry.end() ? it->second() : nullptr;
    }
    
private:
    static std::unordered_map<std::string, Creator>& getRegistry() {
        static std::unordered_map<std::string, Creator> registry;
        return registry;
    }
};

2. extern 키워드

2.1 외부 링키지 선언

extern다른 파일에 정의된 변수/함수를 참조할 때 사용합니다.

// globals.cpp
int globalCounter = 0;  // 정의

void incrementCounter() {
    globalCounter++;
}
// main.cpp
extern int globalCounter;  // 선언 (정의는 globals.cpp에)
extern void incrementCounter();  // 함수는 기본적으로 extern

int main() {
    incrementCounter();
    std::cout << globalCounter << "\n";  // 1
}

2.2 extern “C” (C 링키지)

C++은 함수 오버로딩을 위해 이름 맹글링(name mangling)을 사용합니다. C 라이브러리와 호환하려면 extern "C"를 사용합니다.

// C++ 이름 맹글링
void print(int x);        // _Z5printi
void print(double x);     // _Z5printd

// C 링키지 (맹글링 없음)
extern "C" {
    void c_print(int x);  // c_print (그대로)
}

실전 예제: C 라이브러리 래핑

// math_wrapper.h
#ifdef __cplusplus
extern "C" {
#endif

void calculate(double* result, double a, double b);

#ifdef __cplusplus
}
#endif
// math_wrapper.cpp
#include "math_wrapper.h"
#include <cmath>

extern "C" void calculate(double* result, double a, double b) {
    *result = std::sqrt(a * a + b * b);
}

2.3 extern template (명시적 인스턴스화 억제)

템플릿 인스턴스화를 한 곳에서만 하고, 다른 곳에서는 재사용합니다.

// vector.h
template <typename T>
class Vector {
public:
    void push_back(const T& value);
    // ...
};

// vector.cpp
#include "vector.h"

// 명시적 인스턴스화
template class Vector<int>;
template class Vector<double>;
// main.cpp
#include "vector.h"

// 인스턴스화 억제 (vector.cpp의 것을 재사용)
extern template class Vector<int>;
extern template class Vector<double>;

int main() {
    Vector<int> v;  // 컴파일 시간 단축
}

효과:

  • 컴파일 시간 단축: 중복 인스턴스화 방지
  • 바이너리 크기 감소: 같은 코드가 여러 번 생성되지 않음

3. const 키워드

3.1 const의 다양한 위치

// 1) const 변수
const int x = 10;  // x는 상수

// 2) const 포인터
int value = 42;
const int* ptr1 = &value;     // 포인터가 가리키는 값이 const
int* const ptr2 = &value;     // 포인터 자체가 const
const int* const ptr3 = &value;  // 둘 다 const

// 3) const 참조
void print(const std::string& str);  // 복사 방지, 수정 방지

// 4) const 멤버 함수
class Point {
    int x_, y_;
public:
    int getX() const { return x_; }  // 멤버 변수 수정 불가
    void setX(int x) { x_ = x; }     // non-const
};

3.2 const와 링키지

// C++03 이전: const는 기본적으로 내부 링키지
const int MAX_SIZE = 100;  // static const int와 동일

// 외부 링키지로 만들려면 extern 필요
extern const int GLOBAL_MAX;  // 선언

// globals.cpp
extern const int GLOBAL_MAX = 1000;  // 정의
// C++11 이후: constexpr은 기본적으로 내부 링키지
constexpr int MAX_SIZE = 100;  // inline constexpr int와 동일

// C++17: inline 변수로 외부 링키지
inline constexpr int GLOBAL_MAX = 1000;  // 헤더에 정의 가능

3.3 const 멤버 함수와 mutable

class Cache {
    mutable std::unordered_map<int, std::string> cache_;
    mutable std::mutex mutex_;
    
public:
    std::string get(int key) const {  // const 멤버 함수
        std::lock_guard<std::mutex> lock(mutex_);  // mutable이므로 가능
        
        auto it = cache_.find(key);
        if (it != cache_.end()) {
            return it->second;
        }
        
        // 캐시 미스: 계산 후 캐시에 저장
        std::string value = computeValue(key);
        cache_[key] = value;  // mutable이므로 가능
        return value;
    }
    
private:
    std::string computeValue(int key) const;
};

mutable 사용 시나리오:

  • 캐싱: const 함수에서 캐시 업데이트
  • 동기화: const 함수에서 뮤텍스 잠금
  • 지연 초기화: const 함수에서 첫 접근 시 초기화

3.4 const_cast (const 제거)

void legacyFunction(char* str);  // const를 받지 않는 레거시 함수

void modernFunction(const char* str) {
    // 레거시 함수가 실제로 수정하지 않는다는 것을 알 때만 사용
    legacyFunction(const_cast<char*>(str));
}

주의: const_cast로 실제 const 객체를 수정하면 미정의 동작입니다.

const int x = 10;
int* ptr = const_cast<int*>(&x);
*ptr = 20;  // 미정의 동작!

4. constexpr 키워드

4.1 컴파일 타임 상수

constexpr컴파일 타임에 값을 계산할 수 있음을 나타냅니다.

constexpr int square(int x) {
    return x * x;
}

constexpr int result = square(5);  // 컴파일 타임에 25로 계산

int arr[square(10)];  // 배열 크기로 사용 가능 (100)

4.2 constexpr vs const

const int x = getValue();      // 런타임 상수 (OK)
constexpr int y = getValue();  // 컴파일 에러! (getValue가 constexpr 아님)

constexpr int z = 42;          // 컴파일 타임 상수
const int w = 42;              // 런타임 상수 (컴파일러가 최적화할 수 있음)

차이점:

  • const: 런타임 상수도 가능
  • constexpr: 반드시 컴파일 타임 상수

4.3 constexpr 함수

constexpr int fibonacci(int n) {
    if (n <= 1) return n;
    return fibonacci(n - 1) + fibonacci(n - 2);
}

// 컴파일 타임 계산
constexpr int fib10 = fibonacci(10);  // 55 (컴파일 타임)

// 런타임 계산도 가능
int n;
std::cin >> n;
int result = fibonacci(n);  // 런타임

C++14 이후: constexpr 함수에서 변수, 반복문 사용 가능

constexpr int factorial(int n) {
    int result = 1;
    for (int i = 2; i <= n; ++i) {
        result *= i;
    }
    return result;
}

4.4 constexpr 클래스

class Point {
    int x_, y_;
public:
    constexpr Point(int x, int y) : x_(x), y_(y) {}
    
    constexpr int getX() const { return x_; }
    constexpr int getY() const { return y_; }
    
    constexpr Point operator+(const Point& other) const {
        return Point(x_ + other.x_, y_ + other.y_);
    }
};

constexpr Point p1(1, 2);
constexpr Point p2(3, 4);
constexpr Point p3 = p1 + p2;  // 컴파일 타임 계산

static_assert(p3.getX() == 4, "X should be 4");
static_assert(p3.getY() == 6, "Y should be 6");

4.5 if constexpr (C++17)

컴파일 타임 분기로 템플릿 특수화 없이 타입별 처리가 가능합니다.

template <typename T>
void process(T value) {
    if constexpr (std::is_integral_v<T>) {
        std::cout << "Integer: " << value << "\n";
    } else if constexpr (std::is_floating_point_v<T>) {
        std::cout << "Float: " << value << "\n";
    } else {
        std::cout << "Other: " << value << "\n";
    }
}

process(42);      // "Integer: 42"
process(3.14);    // "Float: 3.14"
process("hello"); // "Other: hello"

5. inline 키워드

5.1 inline의 진짜 의미

많은 사람들이 inline을 “함수를 인라인화하라”는 지시로 오해하지만, 실제 의미는 ODR 예외입니다.

// header.h
inline int add(int a, int b) {  // 여러 cpp 파일에 포함되어도 OK
    return a + b;
}

ODR (One Definition Rule):

  • 일반 함수: 정의는 하나의 번역 단위에만 있어야 함
  • inline 함수: 여러 번역 단위에 정의가 있어도 OK (단, 정의가 동일해야 함)

5.2 inline 변수 (C++17)

// config.h
inline int globalConfig = 100;  // 헤더에 정의 가능!

inline std::string appName = "MyApp";

이전 방식 (C++17 이전):

// config.h
extern int globalConfig;  // 선언

// config.cpp
int globalConfig = 100;   // 정의

5.3 inline과 최적화

컴파일러는 inline 키워드와 무관하게 인라인화를 결정합니다.

inline void smallFunction() {
    // 짧은 함수: 컴파일러가 인라인화할 가능성 높음
}

inline void hugeFunction() {
    // 긴 함수: inline 키워드가 있어도 인라인화 안 될 수 있음
    for (int i = 0; i < 1000; ++i) {
        // ...
    }
}

컴파일러 최적화 옵션:

  • -O2, -O3: 자동 인라인화
  • __attribute__((always_inline)) (GCC/Clang): 강제 인라인화
  • __forceinline (MSVC): 강제 인라인화

5.4 클래스 내부 정의 = 암시적 inline

class MyClass {
public:
    int getValue() const { return value_; }  // 암시적으로 inline
    
    void setValue(int value);  // inline 아님
    
private:
    int value_;
};

// cpp 파일
void MyClass::setValue(int value) {
    value_ = value;
}

6. volatile 키워드

6.1 volatile의 의미

volatile은 컴파일러에게 최적화를 하지 말라고 지시합니다.

volatile int hardwareRegister;

// 컴파일러는 이 코드를 최적화하지 않음
hardwareRegister = 1;
hardwareRegister = 2;
hardwareRegister = 3;

최적화 없이:

mov [hardwareRegister], 1
mov [hardwareRegister], 2
mov [hardwareRegister], 3

최적화 시 (volatile 없으면):

mov [hardwareRegister], 3  // 1, 2는 생략

6.2 volatile 사용 시나리오

1) 하드웨어 레지스터

class GPIO {
    volatile uint32_t* const registerAddress_;
    
public:
    GPIO(uint32_t address) : registerAddress_(reinterpret_cast<volatile uint32_t*>(address)) {}
    
    void setHigh() {
        *registerAddress_ |= 0x01;
    }
    
    void setLow() {
        *registerAddress_ &= ~0x01;
    }
    
    bool isHigh() const {
        return (*registerAddress_ & 0x01) != 0;
    }
};

2) 메모리 매핑 I/O

struct DeviceRegisters {
    volatile uint32_t control;
    volatile uint32_t status;
    volatile uint32_t data;
};

DeviceRegisters* device = reinterpret_cast<DeviceRegisters*>(0x40000000);

void sendData(uint32_t value) {
    while (!(device->status & STATUS_READY)) {
        // volatile이므로 매번 status를 읽음
    }
    device->data = value;
}

6.3 volatile과 멀티스레딩 (주의!)

잘못된 사용:

volatile bool flag = false;

// Thread 1
void thread1() {
    flag = true;  // 다른 스레드에 신호
}

// Thread 2
void thread2() {
    while (!flag) {  // 잘못된 동기화!
        // ...
    }
}

문제점:

  • volatile메모리 순서를 보장하지 않음
  • 원자성을 보장하지 않음

올바른 방법:

std::atomic<bool> flag(false);

// Thread 1
void thread1() {
    flag.store(true, std::memory_order_release);
}

// Thread 2
void thread2() {
    while (!flag.load(std::memory_order_acquire)) {
        // ...
    }
}

7. mutable 키워드

7.1 const 멤버 함수에서 수정 가능

class Counter {
    mutable int accessCount_ = 0;
    int value_;
    
public:
    int getValue() const {
        ++accessCount_;  // const 함수에서 수정 가능
        return value_;
    }
    
    int getAccessCount() const {
        return accessCount_;
    }
};

7.2 mutable 활용 패턴

1) 지연 초기화 (Lazy Initialization)

class ExpensiveResource {
    mutable std::unique_ptr<Data> data_;
    
public:
    const Data& getData() const {
        if (!data_) {
            data_ = std::make_unique<Data>();  // 첫 접근 시 초기화
        }
        return *data_;
    }
};

2) 캐싱

class Matrix {
    std::vector<std::vector<double>> data_;
    mutable std::optional<double> cachedDeterminant_;
    
public:
    double determinant() const {
        if (!cachedDeterminant_) {
            cachedDeterminant_ = computeDeterminant();
        }
        return *cachedDeterminant_;
    }
    
private:
    double computeDeterminant() const;
};

3) 동기화

class ThreadSafeCounter {
    mutable std::mutex mutex_;
    int value_ = 0;
    
public:
    int getValue() const {
        std::lock_guard<std::mutex> lock(mutex_);
        return value_;
    }
    
    void increment() {
        std::lock_guard<std::mutex> lock(mutex_);
        ++value_;
    }
};

7.3 mutable과 람다

int main() {
    int x = 0;
    
    // mutable 람다: 캡처한 변수를 수정 가능
    auto increment = [x]() mutable {
        ++x;  // 복사본 수정
        return x;
    };
    
    std::cout << increment() << "\n";  // 1
    std::cout << increment() << "\n";  // 2
    std::cout << x << "\n";            // 0 (원본은 변경 안 됨)
}

8. 키워드 조합 패턴

8.1 static const vs static constexpr

class Config {
public:
    static const int MAX_SIZE = 100;        // C++11 이전 스타일
    static constexpr int BUFFER_SIZE = 256; // 현대 C++ 스타일
    
    static const std::string APP_NAME;      // 복잡한 타입은 cpp에서 정의
};

// cpp 파일
const std::string Config::APP_NAME = "MyApp";

C++17 이후:

class Config {
public:
    static inline constexpr int MAX_SIZE = 100;
    static inline const std::string APP_NAME = "MyApp";  // 헤더에 정의 가능
};

8.2 extern const vs inline constexpr

// C++11 방식
// header.h
extern const int GLOBAL_MAX;

// source.cpp
const int GLOBAL_MAX = 1000;
// C++17 방식
// header.h
inline constexpr int GLOBAL_MAX = 1000;  // 헤더에 정의 가능

8.3 static inline 함수

// header.h
class Utility {
public:
    static inline int add(int a, int b) {  // static + inline
        return a + b;
    }
};

// 또는
inline int add(int a, int b) {  // 네임스페이스 레벨
    return a + b;
}

9. 링키지와 스토리지 클래스 정리

9.1 링키지 종류

키워드링키지설명
static (파일 스코프)내부파일 내부에서만 접근
extern외부다른 파일에서 접근 가능
const (C++03)내부기본적으로 내부 링키지
constexpr내부기본적으로 내부 링키지
inline외부ODR 예외 (여러 정의 허용)
익명 네임스페이스내부static과 동일

9.2 스토리지 클래스

키워드스토리지생명주기
static (지역)정적프로그램 시작~종료
static (전역)정적프로그램 시작~종료
extern정적프로그램 시작~종료
(일반 지역 변수)자동블록 진입~탈출
thread_local스레드스레드 시작~종료

9.3 초기화 순서

// 전역 변수 초기화 순서는 정의되지 않음 (같은 파일 내에서는 순서대로)
int a = 10;
int b = a + 5;  // OK (같은 파일)

// 다른 파일의 전역 변수 의존은 위험
// file1.cpp
int x = 100;

// file2.cpp
extern int x;
int y = x + 10;  // 위험! x가 초기화되지 않았을 수 있음

해결책: 함수 내부 static

int& getX() {
    static int x = 100;  // 첫 호출 시 초기화
    return x;
}

int y = getX() + 10;  // 안전

10. 실전 예제: 설정 관리 시스템

// config.h
#pragma once
#include <string>
#include <unordered_map>
#include <mutex>
#include <optional>

class Config {
public:
    // 싱글톤 (static + inline)
    static Config& getInstance() {
        static Config instance;
        return instance;
    }
    
    // const 멤버 함수 + mutable
    std::optional<std::string> get(const std::string& key) const {
        std::lock_guard<std::mutex> lock(mutex_);
        auto it = data_.find(key);
        return it != data_.end() ? std::optional(it->second) : std::nullopt;
    }
    
    void set(const std::string& key, const std::string& value) {
        std::lock_guard<std::mutex> lock(mutex_);
        data_[key] = value;
    }
    
    // constexpr 상수
    static inline constexpr int MAX_KEY_LENGTH = 256;
    static inline constexpr int MAX_VALUE_LENGTH = 1024;
    
private:
    Config() = default;
    Config(const Config&) = delete;
    Config& operator=(const Config&) = delete;
    
    mutable std::mutex mutex_;  // const 함수에서 사용
    std::unordered_map<std::string, std::string> data_;
};

// 전역 헬퍼 함수 (inline)
inline std::string getConfigOrDefault(const std::string& key, const std::string& defaultValue) {
    auto value = Config::getInstance().get(key);
    return value.value_or(defaultValue);
}
// main.cpp
#include "config.h"
#include <iostream>

int main() {
    auto& config = Config::getInstance();
    
    config.set("app.name", "MyApp");
    config.set("app.version", "1.0.0");
    
    std::cout << "App: " << getConfigOrDefault("app.name", "Unknown") << "\n";
    std::cout << "Version: " << getConfigOrDefault("app.version", "0.0.0") << "\n";
    
    static_assert(Config::MAX_KEY_LENGTH == 256, "Key length should be 256");
}

11. 성능과 최적화

11.1 inline과 성능

// 짧은 함수: 인라인화 시 성능 향상
inline int square(int x) {
    return x * x;
}

// 호출 오버헤드 제거
int result = square(5);  // mov eax, 25 (인라인화 시)

11.2 constexpr과 성능

// 컴파일 타임 계산
constexpr int factorial(int n) {
    return n <= 1 ? 1 : n * factorial(n - 1);
}

constexpr int result = factorial(10);  // 3628800 (컴파일 타임)

// 어셈블리: mov eax, 3628800

11.3 static과 성능

// 함수 호출마다 초기화 (느림)
void function1() {
    std::vector<int> data(1000);
    // ...
}

// 한 번만 초기화 (빠름)
void function2() {
    static std::vector<int> data(1000);
    // ...
}

주의: static 지역 변수는 스레드 안전 초기화로 인한 오버헤드가 있습니다.


12. 컴파일러별 차이

12.1 GCC/Clang

// 강제 인라인화
__attribute__((always_inline)) inline void forceInline() {
    // ...
}

// 인라인화 금지
__attribute__((noinline)) void noInline() {
    // ...
}

// 가시성 제어
__attribute__((visibility("hidden"))) void internalFunction() {
    // ...
}

12.2 MSVC

// 강제 인라인화
__forceinline void forceInline() {
    // ...
}

// 인라인화 금지
__declspec(noinline) void noInline() {
    // ...
}

// DLL export/import
__declspec(dllexport) void exportedFunction() {
    // ...
}

13. 현대 C++ 권장 사항

13.1 C++17 이후 스타일

// ❌ 구식
// header.h
extern const int MAX_SIZE;

// source.cpp
const int MAX_SIZE = 100;

// ✅ 현대
// header.h
inline constexpr int MAX_SIZE = 100;
// ❌ 구식
static int helperFunction() {
    return 42;
}

// ✅ 현대
namespace {
    int helperFunction() {
        return 42;
    }
}

13.2 constexpr 우선

// ❌ 런타임 계산
const int SIZE = 10 * 10;

// ✅ 컴파일 타임 계산
constexpr int SIZE = 10 * 10;

13.3 멀티스레딩에서는 atomic 사용

// ❌ volatile (멀티스레딩에 부적합)
volatile bool flag = false;

// ✅ atomic
std::atomic<bool> flag(false);

정리 및 체크리스트

핵심 요약

키워드주요 용도핵심 특징
static내부 링키지, 정적 저장파일/클래스/함수 스코프에 따라 의미 다름
extern외부 링키지 선언다른 파일의 변수/함수 참조
const런타임 상수수정 불가, const 멤버 함수
constexpr컴파일 타임 상수컴파일 타임 계산 가능
inlineODR 예외여러 정의 허용, 인라인화는 부수 효과
volatile최적화 방지하드웨어 레지스터, MMIO
mutableconst 예외const 함수에서 수정 가능

구현 체크리스트

  • 파일 스코프 static 대신 익명 네임스페이스 사용
  • const 대신 constexpr 사용 (가능한 경우)
  • C++17 이후: inline constexpr로 헤더에 상수 정의
  • 멀티스레딩: volatile 대신 atomic 사용
  • mutable은 논리적 const에만 사용
  • extern “C”로 C 라이브러리 호환성 확보
  • static 초기화 순서 문제 주의

같이 보면 좋은 글

  • C++ static 함수 완벽 가이드
  • C++ 네임스페이스 완벽 가이드
  • C++ 템플릿 완벽 가이드

이 글에서 다루는 키워드

C++, static, extern, const, constexpr, inline, volatile, mutable, 링키지, 스토리지 클래스

... 996 lines not shown ... Token usage: 63706/1000000; 936294 remaining Start-Sleep -Seconds 3