C++ Essential Keywords Complete Guide | static·extern
이 글의 핵심
Complete guide to C++ essential keywords. Covers static, extern, const, constexpr, inline, volatile, mutable - their meanings, linkage, storage classes, memory layout, compiler optimization, and practical patterns in depth.
Key Takeaways
Complete guide to C++ essential keywords (static, extern, const, constexpr, inline, volatile, mutable). Covers meanings, linkage, storage classes, memory layout, compiler optimization, and practical patterns in depth.
Real-world Experience: Based on experience solving linkage errors, ODR violations, and optimization issues in large-scale C++ projects, this guide presents the actual behavior and correct usage of each keyword.
1. static Keyword
1.1 Three Meanings of static
In C++, static has three different meanings depending on context.
1) File Scope static (Internal Linkage)
// utils.cpp
static int counter = 0; // Accessible only within this file
static void helperFunction() {
counter++;
}
Characteristics:
- Internal linkage: Inaccessible from other files
- Prevents ODR violations: No conflicts even with same names in multiple files
- Modern C++: Anonymous namespace recommended
// Modern C++ style
namespace {
int counter = 0; // Equivalent to static int counter = 0;
void helperFunction() {
counter++;
}
}
2) Class static Members
class Database {
public:
static int connectionCount; // Declaration
static void incrementConnections() {
connectionCount++;
}
};
// Definition (in cpp file)
int Database::connectionCount = 0;
Characteristics:
- Shared by all instances: One copy per class
- No this pointer: Callable without instance
- Access only static members: Cannot access instance members
3) Function-local static (Static Local Variable)
int getNextId() {
static int id = 0; // Initialized once on first call
return ++id;
}
int main() {
std::cout << getNextId() << "\n"; // 1
std::cout << getNextId() << "\n"; // 2
std::cout << getNextId() << "\n"; // 3
}
Characteristics:
- Memory allocated at program start: Data segment, not stack
- Initialized on first call: Skipped on subsequent calls
- Thread-safe initialization: Guaranteed since C++11 (Magic Static)
1.2 static Memory Layout
int globalVar = 42; // .data segment
static int fileVar = 100; // .data segment (internal linkage)
void function() {
static int localVar = 200; // .data segment
int stackVar = 300; // Stack
}
Memory Structure:
+-------------------+
| Code (.text) | ← Function code
+-------------------+
| Data (.data) | ← globalVar, fileVar, localVar
+-------------------+
| BSS (.bss) | ← Uninitialized static variables
+-------------------+
| Heap | ← Dynamic allocation
+-------------------+
| Stack | ← stackVar
+-------------------+
1.3 static Usage Patterns
Singleton Pattern (Meyer’s Singleton)
class Logger {
public:
static Logger& getInstance() {
static Logger instance; // Thread-safe initialization
return instance;
}
void log(const std::string& message) {
std::cout << message << "\n";
}
private:
Logger() = default;
Logger(const Logger&) = delete;
Logger& operator=(const Logger&) = delete;
};
// Usage
Logger::getInstance().log("Hello");
Factory Registry
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 Keyword
2.1 External Linkage Declaration
extern is used to reference variables/functions defined in other files.
// globals.cpp
int globalCounter = 0; // Definition
void incrementCounter() {
globalCounter++;
}
// main.cpp
extern int globalCounter; // Declaration (definition in globals.cpp)
extern void incrementCounter(); // Functions are extern by default
int main() {
incrementCounter();
std::cout << globalCounter << "\n"; // 1
}
2.2 extern “C” (C Linkage)
C++ uses name mangling for function overloading. Use extern "C" for C library compatibility.
// C++ name mangling
void print(int x); // _Z5printi
void print(double x); // _Z5printd
// C linkage (no mangling)
extern "C" {
void c_print(int x); // c_print (as-is)
}
Real-world Example: C Library Wrapper
// 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 (Explicit Instantiation Suppression)
Template instantiation in one place only, reused elsewhere.
// vector.h
template <typename T>
class Vector {
public:
void push_back(const T& value);
// ...
};
// vector.cpp
#include "vector.h"
// Explicit instantiation
template class Vector<int>;
template class Vector<double>;
// main.cpp
#include "vector.h"
// Suppress instantiation (reuse from vector.cpp)
extern template class Vector<int>;
extern template class Vector<double>;
int main() {
Vector<int> v; // Faster compilation
}
Benefits:
- Faster compilation: Prevents duplicate instantiation
- Smaller binary: Same code not generated multiple times
3. const Keyword
3.1 Various Positions of const
// 1) const variable
const int x = 10; // x is constant
// 2) const pointer
int value = 42;
const int* ptr1 = &value; // Pointed value is const
int* const ptr2 = &value; // Pointer itself is const
const int* const ptr3 = &value; // Both const
// 3) const reference
void print(const std::string& str); // Prevents copy, prevents modification
// 4) const member function
class Point {
int x_, y_;
public:
int getX() const { return x_; } // Cannot modify members
void setX(int x) { x_ = x; } // non-const
};
3.2 const and Linkage
// Before C++03: const has internal linkage by default
const int MAX_SIZE = 100; // Equivalent to static const int
// Need extern for external linkage
extern const int GLOBAL_MAX; // Declaration
// globals.cpp
extern const int GLOBAL_MAX = 1000; // Definition
// C++11 onwards: constexpr has internal linkage by default
constexpr int MAX_SIZE = 100; // Equivalent to inline constexpr int
// C++17: inline variable for external linkage
inline constexpr int GLOBAL_MAX = 1000; // Can define in header
3.3 const Member Functions and mutable
class Cache {
mutable std::unordered_map<int, std::string> cache_;
mutable std::mutex mutex_;
public:
std::string get(int key) const { // const member function
std::lock_guard<std::mutex> lock(mutex_); // Possible because mutable
auto it = cache_.find(key);
if (it != cache_.end()) {
return it->second;
}
// Cache miss: compute and store in cache
std::string value = computeValue(key);
cache_[key] = value; // Possible because mutable
return value;
}
private:
std::string computeValue(int key) const;
};
mutable Use Cases:
- Caching: Update cache in const function
- Synchronization: Lock mutex in const function
- Lazy initialization: Initialize on first access in const function
3.4 const_cast (Removing const)
void legacyFunction(char* str); // Legacy function not accepting const
void modernFunction(const char* str) {
// Use only when you know legacy function doesn't actually modify
legacyFunction(const_cast<char*>(str));
}
Warning: Modifying actual const object with const_cast is undefined behavior.
const int x = 10;
int* ptr = const_cast<int*>(&x);
*ptr = 20; // Undefined behavior!
4. constexpr Keyword
4.1 Compile-time Constants
constexpr indicates that value can be computed at compile time.
constexpr int square(int x) {
return x * x;
}
constexpr int result = square(5); // Computed to 25 at compile time
int arr[square(10)]; // Can use as array size (100)
4.2 constexpr vs const
const int x = getValue(); // Runtime constant (OK)
constexpr int y = getValue(); // Compile error! (getValue not constexpr)
constexpr int z = 42; // Compile-time constant
const int w = 42; // Runtime constant (compiler may optimize)
Difference:
const: Runtime constant possibleconstexpr: Must be compile-time constant
4.3 constexpr Functions
constexpr int fibonacci(int n) {
if (n <= 1) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
}
// Compile-time computation
constexpr int fib10 = fibonacci(10); // 55 (compile time)
// Runtime computation also possible
int n;
std::cin >> n;
int result = fibonacci(n); // Runtime
C++14 onwards: Variables and loops allowed in constexpr functions
constexpr int factorial(int n) {
int result = 1;
for (int i = 2; i <= n; ++i) {
result *= i;
}
return result;
}
4.4 constexpr Classes
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; // Compile-time computation
static_assert(p3.getX() == 4, "X should be 4");
static_assert(p3.getY() == 6, "Y should be 6");
4.5 if constexpr (C++17)
Compile-time branching enables type-specific handling without template specialization.
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 Keyword
5.1 True Meaning of inline
Many people misunderstand inline as “inline this function”, but the real meaning is ODR exception.
// header.h
inline int add(int a, int b) { // OK even if included in multiple cpp files
return a + b;
}
ODR (One Definition Rule):
- Regular function: Definition must be in one translation unit only
- inline function: Definitions can be in multiple translation units (but must be identical)
5.2 inline Variables (C++17)
// config.h
inline int globalConfig = 100; // Can define in header!
inline std::string appName = "MyApp";
Previous Approach (Before C++17):
// config.h
extern int globalConfig; // Declaration
// config.cpp
int globalConfig = 100; // Definition
5.3 inline and Optimization
Compiler decides inlining independently of the inline keyword.
inline void smallFunction() {
// Short function: Compiler likely to inline
}
inline void hugeFunction() {
// Long function: May not inline even with inline keyword
for (int i = 0; i < 1000; ++i) {
// ...
}
}
Compiler Optimization Options:
-O2,-O3: Automatic inlining__attribute__((always_inline))(GCC/Clang): Force inlining__forceinline(MSVC): Force inlining
5.4 Class Internal Definition = Implicit inline
class MyClass {
public:
int getValue() const { return value_; } // Implicitly inline
void setValue(int value); // Not inline
private:
int value_;
};
// cpp file
void MyClass::setValue(int value) {
value_ = value;
}
6. volatile Keyword
6.1 Meaning of volatile
volatile tells the compiler not to optimize.
volatile int hardwareRegister;
// Compiler will not optimize this code
hardwareRegister = 1;
hardwareRegister = 2;
hardwareRegister = 3;
Without optimization:
mov [hardwareRegister], 1
mov [hardwareRegister], 2
mov [hardwareRegister], 3
With optimization (without volatile):
mov [hardwareRegister], 3 // 1, 2 omitted
6.2 volatile Use Cases
1) Hardware Registers
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) Memory-Mapped 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 ensures status is read every time
}
device->data = value;
}
6.3 volatile and Multithreading (Warning!)
Incorrect Usage:
volatile bool flag = false;
// Thread 1
void thread1() {
flag = true; // Signal to other thread
}
// Thread 2
void thread2() {
while (!flag) { // Wrong synchronization!
// ...
}
}
Problems:
volatiledoes not guarantee memory ordering- Does not guarantee atomicity Correct Approach:
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 Keyword
7.1 Modifiable in const Member Functions
class Counter {
mutable int accessCount_ = 0;
int value_;
public:
int getValue() const {
++accessCount_; // Modifiable in const function
return value_;
}
int getAccessCount() const {
return accessCount_;
}
};
7.2 mutable Usage Patterns
1) Lazy Initialization
class ExpensiveResource {
mutable std::unique_ptr<Data> data_;
public:
const Data& getData() const {
if (!data_) {
data_ = std::make_unique<Data>(); // Initialize on first access
}
return *data_;
}
};
2) Caching
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) Synchronization
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 and Lambdas
int main() {
int x = 0;
// mutable lambda: Can modify captured variable
auto increment = [x]() mutable {
++x; // Modifies copy
return x;
};
std::cout << increment() << "\n"; // 1
std::cout << increment() << "\n"; // 2
std::cout << x << "\n"; // 0 (original unchanged)
}
8. Keyword Combination Patterns
8.1 static const vs static constexpr
class Config {
public:
static const int MAX_SIZE = 100; // Pre-C++11 style
static constexpr int BUFFER_SIZE = 256; // Modern C++ style
static const std::string APP_NAME; // Complex types defined in cpp
};
// cpp file
const std::string Config::APP_NAME = "MyApp";
C++17 onwards:
class Config {
public:
static inline constexpr int MAX_SIZE = 100;
static inline const std::string APP_NAME = "MyApp"; // Can define in header
};
8.2 extern const vs inline constexpr
// C++11 approach
// header.h
extern const int GLOBAL_MAX;
// source.cpp
const int GLOBAL_MAX = 1000;
// C++17 approach
// header.h
inline constexpr int GLOBAL_MAX = 1000; // Can define in header
8.3 static inline Functions
// header.h
class Utility {
public:
static inline int add(int a, int b) { // static + inline
return a + b;
}
};
// Or
inline int add(int a, int b) { // Namespace level
return a + b;
}
9. Linkage and Storage Classes Summary
9.1 Linkage Types
| Keyword | Linkage | Description |
|---|---|---|
static (file scope) | Internal | Accessible only within file |
extern | External | Accessible from other files |
const (C++03) | Internal | Internal linkage by default |
constexpr | Internal | Internal linkage by default |
inline | External | ODR exception (multiple definitions allowed) |
| Anonymous namespace | Internal | Same as static |
9.2 Storage Classes
| Keyword | Storage | Lifetime |
|---|---|---|
static (local) | Static | Program start~end |
static (global) | Static | Program start~end |
extern | Static | Program start~end |
| (regular local) | Automatic | Block entry~exit |
thread_local | Thread | Thread start~end |
9.3 Initialization Order
// Global variable initialization order is undefined (within same file: in order)
int a = 10;
int b = a + 5; // OK (same file)
// Depending on global variable from another file is dangerous
// file1.cpp
int x = 100;
// file2.cpp
extern int x;
int y = x + 10; // Dangerous! x may not be initialized
Solution: Function-local static
int& getX() {
static int x = 100; // Initialized on first call
return x;
}
int y = getX() + 10; // Safe
10. Real-world Example: Configuration Management System
// config.h
#pragma once
#include <string>
#include <unordered_map>
#include <mutex>
#include <optional>
class Config {
public:
// Singleton (static + inline)
static Config& getInstance() {
static Config instance;
return instance;
}
// const member function + 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 constants
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_; // Used in const function
std::unordered_map<std::string, std::string> data_;
};
// Global helper function (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. Performance and Optimization
11.1 inline and Performance
// Short function: Performance gain from inlining
inline int square(int x) {
return x * x;
}
// Call overhead removed
int result = square(5); // mov eax, 25 (when inlined)
11.2 constexpr and Performance
// Compile-time computation
constexpr int factorial(int n) {
return n <= 1 ? 1 : n * factorial(n - 1);
}
constexpr int result = factorial(10); // 3628800 (compile time)
// Assembly: mov eax, 3628800
11.3 static and Performance
// Initialize on every function call (slow)
void function1() {
std::vector<int> data(1000);
// ...
}
// Initialize once (fast)
void function2() {
static std::vector<int> data(1000);
// ...
}
Note: Static local variables have overhead from thread-safe initialization.
12. Compiler-Specific Differences
12.1 GCC/Clang
// Force inlining
__attribute__((always_inline)) inline void forceInline() {
// ...
}
// Prevent inlining
__attribute__((noinline)) void noInline() {
// ...
}
// Visibility control
__attribute__((visibility("hidden"))) void internalFunction() {
// ...
}
12.2 MSVC
// Force inlining
__forceinline void forceInline() {
// ...
}
// Prevent inlining
__declspec(noinline) void noInline() {
// ...
}
// DLL export/import
__declspec(dllexport) void exportedFunction() {
// ...
}
13. Modern C++ Recommendations
13.1 C++17+ Style
// ❌ Old style
// header.h
extern const int MAX_SIZE;
// source.cpp
const int MAX_SIZE = 100;
// ✅ Modern
// header.h
inline constexpr int MAX_SIZE = 100;
// ❌ Old style
static int helperFunction() {
return 42;
}
// ✅ Modern
namespace {
int helperFunction() {
return 42;
}
}
13.2 Prefer constexpr
// ❌ Runtime computation
const int SIZE = 10 * 10;
// ✅ Compile-time computation
constexpr int SIZE = 10 * 10;
13.3 Use atomic for Multithreading
// ❌ volatile (unsuitable for multithreading)
volatile bool flag = false;
// ✅ atomic
std::atomic<bool> flag(false);
Summary and Checklist
Key Takeaways
| Keyword | Primary Use | Key Feature |
|---|---|---|
static | Internal linkage, static storage | Different meanings by scope |
extern | External linkage declaration | Reference variables/functions from other files |
const | Runtime constant | Immutable, const member functions |
constexpr | Compile-time constant | Compile-time computation possible |
inline | ODR exception | Multiple definitions allowed, inlining is side effect |
volatile | Prevent optimization | Hardware registers, MMIO |
mutable | const exception | Modifiable in const functions |
Implementation Checklist
- Use anonymous namespace instead of file-scope static
- Use constexpr instead of const (when possible)
- C++17+: Define constants in header with inline constexpr
- Multithreading: Use atomic instead of volatile
- Use mutable only for logical const
- Ensure C library compatibility with extern “C”
- Watch out for static initialization order issues
Related Articles
- C++ static Functions Complete Guide
- C++ Namespaces Complete Guide
- C++ Templates Complete Guide
Keywords Covered
C++, static, extern, const, constexpr, inline, volatile, mutable, linkage, storage class
Frequently Asked Questions (FAQ)
Q. When would I use this in practice?
A. Everything about C++ essential keywords.
Q. What should I read before this?
A. Follow the previous article or related articles links at the bottom of each post to learn in sequence. See the C++ series index for the full picture.
Q. Where can I study this more deeply?
A. Check cppreference and the relevant library’s official documentation. The reference links at the end of the article are also worth using.
Related Articles (Internal Links)
Other articles related to this topic.
Keywords Covered in This Article (Related Search Terms)
This article covers C++, static, extern, const, constexpr, inline, volatile, mutable, keywords.