Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/MicrosoftDocs/cpp-docs/llms.txt

Use this file to discover all available pages before exploring further.

Before writing real C++ programs, it is essential to understand the rules that govern how names are found, how objects are laid out in memory, and what kinds of expressions can appear on each side of an assignment. This page covers the core concepts that underpin every C++ program: how scope determines where names are visible, how linkage connects definitions across translation units, what the One Definition Rule requires, what the value categories lvalue and rvalue mean, and how alignment and trivial types affect object layout and performance.

Scope

Scope is the region of a program in which a name is visible. C++ defines several scope kinds:
  • Block scope — names declared inside {} braces, visible from the point of declaration to the closing brace
  • Function scope — applies to labels (goto targets)
  • Class scope — names declared inside a class definition
  • Namespace scope — names declared in a namespace, including the global namespace
  • File scope — names with external or internal linkage at the global level
int x = 10;          // namespace (global) scope

void foo() {
    int x = 20;      // block scope — hides outer x
    {
        int x = 30;  // inner block scope — hides both outer x
        // x == 30 here
    }
    // x == 20 here
}
// x == 10 here (global)
The scope resolution operator :: lets you explicitly name the scope of an identifier:
int value = 100;     // global

void example() {
    int value = 42;
    int a = value;      // local: 42
    int b = ::value;    // global: 100
}

Linkage

Linkage determines whether the same name in different translation units refers to the same entity.
LinkageMeaning
ExternalVisible and usable across translation units (the default for non-const variables and functions at namespace scope)
InternalVisible only within the translation unit where it is declared (static or const at namespace scope)
No linkageLocal variables, types declared inside a function
// file1.cpp
extern int counter;          // declaration — uses external linkage
void increment() { ++counter; }

// file2.cpp
int counter = 0;             // definition — external linkage

static int localHelper = 5;  // internal linkage — not visible to file1.cpp
Use extern to declare (but not define) a variable that lives in another translation unit. Place the single definition in exactly one .cpp file and the extern declarations in a shared header.

Translation Units and the ODR

A translation unit is a single .cpp source file after all #include directives have been processed. Each translation unit is compiled independently before being linked. The One Definition Rule (ODR) states:
  • Every program must have exactly one definition of every non-inline function and non-inline variable with external linkage.
  • Classes, inline functions, and templates may be defined in multiple translation units, but all definitions must be identical.
// shapes.h (included in multiple .cpp files — this is allowed)
class Circle {
public:
    explicit Circle(double r) : radius(r) {}
    double area() const;
private:
    double radius;
};

// shapes.cpp (one definition of the non-inline member function)
double Circle::area() const {
    return 3.14159265358979 * radius * radius;
}
Violating the ODR — for example by providing two different definitions of the same function in two .cpp files — results in undefined behavior that may not produce a compiler or linker error in all cases.

Header Files and Include Guards

Because class definitions and inline functions must appear in every translation unit that uses them, they are placed in header files. To prevent a header from being included more than once in the same translation unit, use an include guard or #pragma once:
// myclass.h
#pragma once          // non-standard but universally supported

class MyClass {
public:
    void doWork();
private:
    int m_value = 0;
};
Equivalently, with traditional include guards:
#ifndef MYCLASS_H
#define MYCLASS_H

class MyClass {
public:
    void doWork();
private:
    int m_value = 0;
};

#endif // MYCLASS_H

Lvalues and Rvalues

Every C++ expression belongs to a value category that governs how it can be used. The C++17 standard defines:
  • lvalue — an expression that refers to a memory location and whose address can be taken (e.g., a named variable)
  • prvalue (pure rvalue) — a temporary or literal with no persistent memory location
  • xvalue (expiring value) — an object whose resources can be moved from (e.g., the result of std::move)
  • glvalue = lvalue or xvalue
  • rvalue = prvalue or xvalue
int main()
{
    int i, j, *p;

    // i is an lvalue; 7 is a prvalue.
    i = 7;          // OK

    // j * 4 is a prvalue — cannot appear on the left side.
    // j * 4 = 7;   // Error C2106

    // Dereferenced pointer is an lvalue.
    *p = i;         // OK (assuming p points to valid memory)

    // const lvalue — readable but not writable.
    const int ci = 7;
    // ci = 9;      // Error C3892
}
Rvalue references (&&) bind to rvalues and enable move semantics:
void process(std::string&& s) {
    // s is an rvalue reference — we can move from it
    std::string local = std::move(s);
}

std::string getName() { return "Alice"; }
process(getName());   // OK: temporary (prvalue) binds to &&

Alignment

Alignment is a constraint on which memory addresses are valid for a given type. Misaligned access causes undefined behavior on many architectures and a hardware trap on some. The alignof operator returns the required alignment of a type in bytes, and alignas overrides the default alignment:
#include <iostream>

struct Default {
    char c;
    int  i;
    double d;
};

struct alignas(16) Aligned16 {
    float x, y, z, w;
};

int main()
{
    std::cout << alignof(Default)   << "\n"; // typically 8
    std::cout << alignof(Aligned16) << "\n"; // 16
    std::cout << sizeof(Aligned16)  << "\n"; // 16

    alignas(64) char buffer[64];             // cache-line aligned buffer
}
Use alignas when working with SIMD data types, lock-free data structures, or any type that must reside on a specific memory boundary. The standard guarantees that alignof(T) is always a power of two.

Trivial, Standard-Layout, and POD Types

These categories determine how types interact with low-level features like memcpy, C APIs, and certain compiler optimizations.
A type is trivial if it has a compiler-generated (or = default) default constructor, copy/move constructors, copy/move assignment operators, and destructor — with no virtual functions or virtual base classes. Trivial types can be safely memcpy-ed.
struct Trivial {
    int   x;
    float y;
    // No user-declared constructors, destructors, or virtual functions
};

static_assert(std::is_trivial_v<Trivial>);  // C++17
A type is standard-layout if all members have the same access control, there are no virtual functions or virtual base classes, and all base classes and non-static data members form a compatible layout. Standard-layout types can be safely exchanged with C code.
struct StandardLayout {
public:
    int   id;
    float value;
    // All members are public — standard-layout
};

static_assert(std::is_standard_layout_v<StandardLayout>);
A POD type is both trivial and standard-layout. POD types are the most compatible with C and are safe for use with memcpy, binary serialization, and C-style APIs.
struct Vec3 {
    float x, y, z;
};

static_assert(std::is_trivial_v<Vec3>);
static_assert(std::is_standard_layout_v<Vec3>);
// Vec3 is therefore a POD type

Program Startup and Termination

A C++ program begins execution in the main function. Static and thread-local objects with non-trivial initialization are initialized before main begins; their destructors run after main returns or std::exit is called.
#include <iostream>

struct Logger {
    Logger()  { std::cout << "Logger constructed\n"; }
    ~Logger() { std::cout << "Logger destroyed\n"; }
};

static Logger g_log;   // constructed before main, destroyed after

int main(int argc, char* argv[]) {
    std::cout << "Program running\n";
    return 0;
}
// Output:
// Logger constructed
// Program running
// Logger destroyed
The order of initialization of static objects across translation units is unspecified. Avoid depending on one global object being initialized before another in a different .cpp file — use the construct on first use idiom (returning a static local) to avoid the static initialization order fiasco.

See Also

Build docs developers (and LLMs) love