C++ 内存布局

深入理解 C++ 内存布局对于编写高性能代码至关重要。本文从进程虚拟内存、数据对齐、对象布局等层面系统分析 C++ 的内存组织方式。

典型的进程虚拟内存布局

vbnet 复制代码
High Address
│
├── Kernel Space
│   ├── System calls
│   ├── Interrupt handlers
│   └── Kernel data structures
│
├── Stack(Grows from high to low addresses)
│   ├── Environment variables & command line args
│   ├── Stack frames (function calls)
│   ├── Local variables
│   └── Function parameters
│
├── Memory Mapping Region
│   ├── Shared libraries (.so files)
│   ├── Anonymous mappings (mmap)
│   ├── File mappings
│   └── Dynamic linker
│
├── Heap(Grows from low to high addresses)
│   ├── malloc/new allocated memory
│   ├── Dynamic memory management
│   └── Program break (brk/sbrk)
│
├── Read/Write Data Sections
│   ├── .data Section
│   │   └── Initialized global/static variables
│   │
│   └── .bss(Block Started by Symbol) Section
│       ├── Uninitialized global/static variables
│       └── Zero-initialized global/static variables
│
├── Read-Only Sections
│   ├── .init Section(main 函数执行前需要运行的初始化代码)
│   │
│   ├── .rodata Section(只读数据)
│   │   ├── String literals
│   │   └── const/constexpr variables
│   │
│   └── .text Section
│       ├── Program instructions
│       ├── Function code
│       └── Executable code
│
└── Low Address

典型地址

cpp 复制代码
#include <iostream>
#include <memory>
#include <dlfcn.h>

// .text section: Function instructions stored here
int global_function() { return 42; }

// .rodata section: Read-only data
const char* string_literal = "Hello, World!";
const int const_global = 300;
constexpr int constexpr_global = 400;

// .data section: Initialized global/static variables
int initialized_global = 100;
static int initialized_static = 200;

// .bss section: Uninitialized or zero-initialized variables
int uninitialized_global;
static int uninitialized_static;
int zero_initialized = 0;  // Compiler may optimize this to .bss

int main() {
    // Stack variables: Local variables
    int stack_var = 10;
    char stack_array[1024];

    // Heap memory: Dynamically allocated
    int* heap_ptr = new int(20);
    std::unique_ptr<int[]> smart_ptr(new int[100]);
    
    // Get shared library address (Memory Mapping Region)
    Dl_info info;
    void* libc_func = (void*)printf;
    dladdr(libc_func, &info);
    
    std::cout << "=== Complete Process Memory Layout ===" << std::endl << std::endl;
    
    // Stack Region (highest addresses)
    std::cout << "STACK REGION:" << std::endl;
    std::cout << "  Local variable:     " << &stack_var << std::endl;
    std::cout << "  Stack array:        " << &stack_array << std::endl;
    std::cout << std::endl;
    
    // Memory Mapping Region
    std::cout << "MEMORY MAPPING REGION:" << std::endl;
    std::cout << "  Shared library:     " << info.dli_fbase << " (" << info.dli_fname << ")" << std::endl;
    std::cout << "  printf() function:  " << libc_func << std::endl;
    std::cout << std::endl;
    
    // Heap Region
    std::cout << "HEAP REGION:" << std::endl;
    std::cout << "  new allocation:     " << heap_ptr << std::endl;
    std::cout << "  smart_ptr array:    " << smart_ptr.get() << std::endl;
    std::cout << std::endl;
    
    // Read/Write Data Sections
    std::cout << "READ/WRITE DATA SECTIONS:" << std::endl;
    std::cout << "  .data - Global var:      " << &initialized_global << std::endl;
    std::cout << "  .data - Static var:      " << &initialized_static << std::endl;
    std::cout << "  .bss - Uninit global:    " << &uninitialized_global << std::endl;
    std::cout << "  .bss - Uninit static:    " << &uninitialized_static << std::endl;
    std::cout << "  .bss - Zero init:        " << &zero_initialized << std::endl;
    std::cout << std::endl;
    
    // Read-Only Sections
    std::cout << "READ-ONLY SECTIONS:" << std::endl;
    std::cout << "  .rodata - String literal: " << (void*)string_literal << " -> \"" << string_literal << "\"" << std::endl;
    std::cout << "  .rodata - const global:   " << &const_global << std::endl;
    std::cout << "  .rodata - constexpr:      " << &constexpr_global << std::endl;
    std::cout << "  .text - Function code:    " << (void*)&global_function << std::endl;
    std::cout << std::endl;

    delete heap_ptr;

    return 0;
}

Memory Mapping Region 的作用

  • 资源共享:多进程共享库文件,节省物理内存
  • 延迟加载:按需加载库函数,减少启动时间
  • 文件映射:将文件直接映射到内存,高效访问大文件
  • 进程通信:提供高性能的共享内存机制
cpp 复制代码
// 1. 共享库加载
// 当程序使用动态链接库时,这些库被映射到此区域
#include <iostream>  // libstdc++.so 会被映射到这里
#include <pthread.h> // libpthread.so 会被映射到这里

// 2. 文件内存映射 (mmap)
#include <sys/mman.h>
#include <fcntl.h>

void file_mapping_example() {
    int fd = open("data.txt", O_RDONLY);
    // 将文件映射到内存,避免read()系统调用
    void* mapped = mmap(nullptr, 4096, PROT_READ, MAP_PRIVATE, fd, 0);
    
    // 优势:
    // - 按需加载:只有访问时才从磁盘读取
    // - 内存共享:多个进程可以共享同一文件的内存映射
    // - 高效访问:避免额外的内存拷贝
    
    munmap(mapped, 4096);
    close(fd);
}

// 3. 匿名映射(进程间通信)
void anonymous_mapping() {
    // 创建共享内存区域,用于进程间通信
    void* shared = mmap(nullptr, 4096, PROT_READ | PROT_WRITE, 
                        MAP_SHARED | MAP_ANONYMOUS, -1, 0);
    
    if (fork() == 0) {
        // 子进程可以访问共享内存
        strcpy((char*)shared, "Hello from child");
        exit(0);
    } else {
        wait(nullptr);
        printf("Parent received: %s\n", (char*)shared);
    }
    
    munmap(shared, 4096);
}

BSS 段的优势

  • 减小文件体积:零值数据不占用磁盘空间
  • 加速程序启动:避免读取大量零数据
  • 写时复制优化:OS 使用 COW 技术,实际写入时才分配内存
  • 标准保证:C++标准确保全局变量零初始化
cpp 复制代码
// 数据段 vs BSS段的对比
int initialized_array[1000000] = {1, 2, 3, /*...*/}; // .data段
int uninitialized_array[1000000];                    // .bss段
int zero_array[1000000] = {0};                       // 编译器优化到.bss段

// 文件大小差异:
// - 包含initialized_array的可执行文件:~4MB
// - 只包含 uninitialized_array 的可执行文件:~几KB

void demonstrate_bss_benefits() {
    // 程序启动时的内存初始化:
    
    // 1. .data段:从磁盘读取初始值
    // 耗时:需要磁盘I/O,文件体积大
    
    // 2. .bss段:内存清零操作
    // 耗时:只需memset(),文件体积小
    memset(bss_start, 0, bss_size); // 一次性清零整个BSS段
}

.init 段的功能

  • 依赖初始化:在 main() 前完成必要的系统准备
  • 全局构造:执行全局对象的构造函数
  • 库初始化:运行静态库和动态库的启动代码
  • 系统配置:注册信号处理器、设置运行环境
cpp 复制代码
// .init段确保程序正确初始化
#include <iostream>

// 全局对象的构造函数会在.init阶段调用
class GlobalService {
public:
    GlobalService() {
        std::cout << "Service initialized before main()" << std::endl;
        setup_logging();
        initialize_resources();
    }
    
private:
    void setup_logging() { /* 设置日志系统 */ }
    void initialize_resources() { /* 初始化资源 */ }
};

GlobalService global_service; // 构造函数在.init阶段执行

// 使用constructor属性的函数
__attribute__((constructor))
void early_initialization() {
    std::cout << "Constructor function called" << std::endl;
    // 系统级初始化
    register_signal_handlers();
    setup_memory_pools();
}

// 程序启动流程:
void program_startup_sequence() {
    // 1. 操作系统加载程序
    // 2. 动态链接器解析依赖
    // 3. 执行.init段代码 ← 这里!
    //    - 全局对象构造函数
    //    - __attribute__((constructor))函数
    //    - 静态库初始化代码
    // 4. 调用main()函数
    // 5. 执行.fini段代码(清理)
}

int main() {
    std::cout << "Main function started" << std::endl;
    return 0;
}

/*
输出顺序:
Service initialized before main()
Constructor function called
Main function started
*/

.rodata 段的意义

  • 内存保护:防止只读数据被意外修改
  • 资源共享:多进程共享只读数据页面
  • 缓存优化:CPU 可对只读数据进行激进缓存
  • 安全保障:保护虚函数表等关键结构

内存对齐

内存对齐 要求数据存储地址必须是其对齐值的整数倍,这是现代计算机架构的基本要求。

为什么要内存对齐

内存对齐从多个维度显著提升程序性能,是现代计算机系统的核心优化技术:

CPU内存访问效率

CPU 按机器字长访问内存(64位系统为8字节),对齐数据可一次完整读取,避免多次访问的性能损失。

cpp 复制代码
#include <chrono>
#include <iostream>

// 性能测试:对齐 vs 未对齐访问
void performance_test() {
    const int iterations = 100000000;
    
    // 对齐的数据访问
    alignas(8) int aligned_data[1000];
    auto start = std::chrono::high_resolution_clock::now();
    for (int i = 0; i < iterations; ++i) {
        volatile int value = aligned_data[i % 1000];  // 防止编译器优化
    }
    auto end = std::chrono::high_resolution_clock::now();
    auto aligned_time = std::chrono::duration_cast<std::chrono::microseconds>(end - start);
    
    // 未对齐的数据访问(人为制造未对齐)
    char buffer[4004];
    int* misaligned_data = reinterpret_cast<int*>(buffer + 1);  // 故意偏移1字节
    start = std::chrono::high_resolution_clock::now();
    for (int i = 0; i < iterations; ++i) {
        volatile int value = misaligned_data[i % 1000];
    }
    end = std::chrono::high_resolution_clock::now();
    auto misaligned_time = std::chrono::duration_cast<std::chrono::microseconds>(end - start);
    
    std::cout << "对齐访问耗时: " << aligned_time.count() << " 微秒" << std::endl;
    std::cout << "未对齐访问耗时: " << misaligned_time.count() << " 微秒" << std::endl;
    std::cout << "性能差异: " << (double)misaligned_time.count() / aligned_time.count() << "x" << std::endl;
}

/*
典型结果(x86-64):
对齐访问耗时: 45 微秒
未对齐访问耗时: 67 微秒  
性能差异: 1.49x

性能损失原因:
- CPU 需要多次内存读取
- 额外的位移和合并操作
- 增加缓存未命中概率
*/

缓存行对齐优化

现代 CPU 缓存行为64字节,正确对齐避免跨缓存行访问,消除假共享性能问题。

cpp 复制代码
#include <thread>
#include <atomic>
#include <vector>

// 缓存行大小(大多数现代CPU)
constexpr size_t CACHE_LINE_SIZE = 64;

// 假共享问题演示
struct BadCounter {
    std::atomic<int> counter1;  // 可能在同一缓存行
    std::atomic<int> counter2;  // 可能在同一缓存行
};

struct GoodCounter {
    alignas(CACHE_LINE_SIZE) std::atomic<int> counter1;  // 独占缓存行
    alignas(CACHE_LINE_SIZE) std::atomic<int> counter2;  // 独占缓存行
};

void cache_line_performance_test() {
    BadCounter bad_counters;
    GoodCounter good_counters;
    
    const int iterations = 10000000;
    
    // 测试假共享情况
    auto start = std::chrono::high_resolution_clock::now();
    std::thread t1([&] {
        for (int i = 0; i < iterations; ++i) {
            bad_counters.counter1.fetch_add(1, std::memory_order_relaxed);
        }
    });
    std::thread t2([&] {
        for (int i = 0; i < iterations; ++i) {
            bad_counters.counter2.fetch_add(1, std::memory_order_relaxed);
        }
    });
    t1.join();
    t2.join();
    auto end = std::chrono::high_resolution_clock::now();
    auto bad_time = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
    
    // 测试避免假共享情况
    start = std::chrono::high_resolution_clock::now();
    std::thread t3([&] {
        for (int i = 0; i < iterations; ++i) {
            good_counters.counter1.fetch_add(1, std::memory_order_relaxed);
        }
    });
    std::thread t4([&] {
        for (int i = 0; i < iterations; ++i) {
            good_counters.counter2.fetch_add(1, std::memory_order_relaxed);
        }
    });
    t3.join();
    t4.join();
    end = std::chrono::high_resolution_clock::now();
    auto good_time = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
    
    std::cout << "假共享耗时: " << bad_time.count() << " 毫秒" << std::endl;
    std::cout << "避免假共享耗时: " << good_time.count() << " 毫秒" << std::endl;
    std::cout << "性能提升: " << (double)bad_time.count() / good_time.count() << "x" << std::endl;
}

/*
典型结果(多核系统):
假共享耗时: 156 毫秒
避免假共享耗时: 89 毫秒
性能提升: 1.75x

原理:假共享导致缓存行在 CPU 核心间频繁传输
*/

SIMD指令性能优化

SIMD 指令严格要求数据对齐,正确对齐释放向量化计算的性能潜力。

cpp 复制代码
#include <immintrin.h>  // AVX指令集
#include <xmmintrin.h>  // SSE指令集

void simd_performance_comparison() {
    const int size = 1000000;
    const int iterations = 1000;
    
    // 对齐的数据(适合SIMD)
    alignas(32) float aligned_array[size];
    alignas(32) float aligned_result[size];
    
    // 未对齐的数据
    float* unaligned_array = new float[size + 8];
    float* unaligned_result = new float[size + 8];
    float* unaligned_src = unaligned_array + 1;  // 故意偏移
    float* unaligned_dst = unaligned_result + 1;
    
    // 初始化数据
    for (int i = 0; i < size; ++i) {
        aligned_array[i] = i * 0.5f;
        unaligned_src[i] = i * 0.5f;
    }
    
    // 测试对齐的SIMD操作
    auto start = std::chrono::high_resolution_clock::now();
    for (int iter = 0; iter < iterations; ++iter) {
        for (int i = 0; i < size; i += 8) {
            __m256 vec = _mm256_load_ps(&aligned_array[i]);      // 对齐加载
            __m256 result = _mm256_mul_ps(vec, _mm256_set1_ps(2.0f));
            _mm256_store_ps(&aligned_result[i], result);         // 对齐存储
        }
    }
    auto end = std::chrono::high_resolution_clock::now();
    auto aligned_time = std::chrono::duration_cast<std::chrono::microseconds>(end - start);
    
    // 测试未对齐的SIMD操作
    start = std::chrono::high_resolution_clock::now();
    for (int iter = 0; iter < iterations; ++iter) {
        for (int i = 0; i < size; i += 8) {
            __m256 vec = _mm256_loadu_ps(&unaligned_src[i]);     // 未对齐加载
            __m256 result = _mm256_mul_ps(vec, _mm256_set1_ps(2.0f));
            _mm256_storeu_ps(&unaligned_dst[i], result);         // 未对齐存储
        }
    }
    end = std::chrono::high_resolution_clock::now();
    auto unaligned_time = std::chrono::duration_cast<std::chrono::microseconds>(end - start);
    
    std::cout << "SIMD对齐访问: " << aligned_time.count() << " 微秒" << std::endl;
    std::cout << "SIMD未对齐访问: " << unaligned_time.count() << " 微秒" << std::endl;
    std::cout << "性能差异: " << (double)unaligned_time.count() / aligned_time.count() << "x" << std::endl;
    
    delete[] unaligned_array;
    delete[] unaligned_result;
}

/*
典型结果:
SIMD对齐访问: 234 微秒
SIMD未对齐访问: 312 微秒
性能差异: 1.33x

优势:对齐版本(_mm256_load_ps)比未对齐版本(_mm256_loadu_ps)快33%
*/

原子操作性能影响

原子操作依赖数据对齐,未对齐数据可能退化为锁机制,严重影响并发性能。

cpp 复制代码
#include <atomic>

void atomic_alignment_impact() {
    // 对齐的原子变量(硬件原子操作)
    alignas(8) std::atomic<long long> aligned_atomic{0};
    
    // 检查原子操作是否无锁
    std::cout << "对齐原子变量无锁: " << aligned_atomic.is_lock_free() << std::endl;
    std::cout << "对齐原子变量地址: " << &aligned_atomic << std::endl;
    std::cout << "地址对齐检查: " << (reinterpret_cast<uintptr_t>(&aligned_atomic) % 8 == 0) << std::endl;
    
    // 模拟未对齐情况(实际中很难构造,因为编译器会自动对齐std::atomic)
    struct UnalignedStruct {
        char padding;
        std::atomic<int> atomic_int;  // 可能未对齐
    };
    
    UnalignedStruct us;
    std::cout << "结构体中原子变量无锁: " << us.atomic_int.is_lock_free() << std::endl;
    
    /*
    性能对比:
    - 对齐原子操作:硬件原子指令,性能最优
    - 未对齐原子操作:退化为锁机制,性能下降10-100倍
    - 特定架构:未对齐原子操作可能直接崩溃
    */
}

C++ 如何内存对齐

自然对齐值

自然对齐值是类型的默认对齐要求,由编译器根据类型特性和目标平台确定。

cpp 复制代码
#include <cstddef>

// 基本类型的自然对齐值
static_assert(alignof(char) == 1);     // char: 1字节对齐
static_assert(alignof(short) == 2);    // short: 2字节对齐
static_assert(alignof(int) == 4);      // int: 4字节对齐
static_assert(alignof(double) == 8);   // double: 8字节对齐

// 结构体继承最大成员的对齐值
struct NaturalExample {
    char a;     // 对齐值: 1
    int b;      // 对齐值: 4
    double c;   // 对齐值: 8
};
static_assert(alignof(NaturalExample) == 8);  // 继承最大对齐值: 8

// 指针的对齐值
static_assert(alignof(void*) == 8);    // 64位系统指针: 8字节对齐
static_assert(alignof(int*) == 8);     // 所有指针对齐值相同

// 数组继承元素类型的对齐值
static_assert(alignof(int[10]) == 4);  // int数组: 4字节对齐
static_assert(alignof(double[5]) == 8); // double数组: 8字节对齐

指定对齐值

指定对齐值通过语言特性或编译器属性显式控制对齐行为

对齐值约束条件:

  • 必须 ≥ 自然对齐值
  • 必须是2的幂(1, 2, 4, 8, 16, 32...)
cpp 复制代码
// 1. alignas 关键字(C++11标准)
struct alignas(16) StructAlign16 {
    char a;
    int b;
};
static_assert(alignof(StructAlign16) == 16);
static_assert(sizeof(StructAlign16) == 16);
static_assert(offsetof(StructAlign16, a) == 0);
static_assert(offsetof(StructAlign16, b) == 4);

struct MemberAlign {
    alignas(16) char a;  // 成员16字节对齐
    int b;
};
static_assert(alignof(MemberAlign) == 16);
static_assert(sizeof(MemberAlign) == 16);
static_assert(offsetof(MemberAlign, a) == 0);
static_assert(offsetof(MemberAlign, b) == 4);

// 2. __attribute__((aligned)) (GCC/Clang)
struct AttributeAlign {
    char a __attribute__((aligned(8)));  // 成员8字节对齐
    int b;
};
static_assert(alignof(AttributeAlign) == 8);
static_assert(sizeof(AttributeAlign) == 8);

// 3. #pragma pack 紧密打包
#pragma pack(push, 1)
struct PackedStruct {
    char a;     // 偏移0
    int b;      // 偏移1 (正常应该是偏移4)
    char c;     // 偏移5
};
#pragma pack(pop)
static_assert(alignof(PackedStruct) == 1);
static_assert(sizeof(PackedStruct) == 6);      // 紧密打包: 6字节
static_assert(offsetof(PackedStruct, b) == 1); // int未对齐可能影响性能

// 指定对齐值的约束
alignas(8) int aligned_int;    // ✓ 8 >= 4 (int的自然对齐值)
// alignas(2) double bad;      // ✗ 2 < 8 (double的自然对齐值)
static_assert(alignof(decltype(aligned_int)) == 8);

对齐规则

对齐规则是编译器内存布局算法,保证所有数据满足对齐要求。

  • 成员地址对齐:每个成员的起始地址 = 对齐值的整数倍
  • 大小对齐:结构体总大小 = 最大对齐值的整数倍
  • 填充插入:自动插入填充字节满足对齐要求
cpp 复制代码
struct AlignmentRules {
    char a;     // 偏移0, 占1字节 (满足1字节对齐)
    // [填充3字节] 让下一个int满足4字节对齐
    int b;      // 偏移4, 占4字节 (4 % 4 == 0, 满足对齐)
    char c;     // 偏移8, 占1字节 (满足1字节对齐)
    // [填充3字节] 让整体大小满足4字节对齐要求
};
static_assert(alignof(AlignmentRules) == 4);
static_assert(sizeof(AlignmentRules) == 12);
static_assert(offsetof(AlignmentRules, a) == 0);
static_assert(offsetof(AlignmentRules, b) == 4);
static_assert(offsetof(AlignmentRules, c) == 8);

// 结构题嵌套
struct ContainerStruct {
    char prefix;                    // 偏移0,占1字节
    // [填充3字节] 为了让AlignmentRuleDemo对齐到4字节边界
    AlignmentRule demo;         // 偏移4,必须4字节对齐!
    char suffix;                    // 偏移16,占1字节
    // [填充3字节] 确保整个结构体大小是4的倍数
};
static_assert(sizeof(ContainerStruct) == 20);
static_assert(alignof(ContainerStruct) == 4);
static_assert(offsetof(ContainerStruct, prefix) == 0);
static_assert(offsetof(ContainerStruct, demo) == 4);
static_assert(offsetof(ContainerStruct, suffix) == 16);

// 数组元素示例
AlignmentRule array[3];         // 每个元素都必须4字节对齐
static_assert(sizeof(array) == 36);
// static_assert((uintptr_t)&array[1] % 4 == 0);
// static_assert((uintptr_t)&array[2] % 4 == 0);

// 动态分配示例
AlignmentRule* ptr = new AlignmentRule;    // new也会遵循4字节对齐
// static_assert(alignof(ptr) == 4);    // 确保分配的地址是4的倍数
delete ptr;

继承体系的内存布局

单继承

cpp 复制代码
class Base {
private:
    int base_member;    // 4 字节
public:
    virtual void virtual_func() {}  // 虚函数
    void normal_func() {}
};

class Derived : public Base {
private:
    char derived_member;    // 1 字节
public:
    void virtual_func() override {}
    void derived_func() {}
};
static_assert(sizeof(Base) == 16);
static_assert(sizeof(Derived) == 16);

/*
Base 类内存布局:
[vptr(8字节)][base_member(4字节)][填充(4字节)]
01234567      89AB              CDEF

Derived 类内存布局:
[vptr(8字节)][base_member(4字节)][derived_member(1字节)][填充(3字节)]
01234567      89AB              C                      DEF

说明:
- vptr:虚函数表指针,总是放在对象开头
- 派生类继承了基类的所有非静态成员
- 虚函数表指针只有一个,指向派生类的虚函数表
*/

多继承

cpp 复制代码
class Base1 {
private:
    int base1_data;
public:
    virtual void base1_func() {}
};

class Base2 {
private:
    int base2_data;
public:
    virtual void base2_func() {}
};

class MultiDerived : public Base1, public Base2 {
private:
    int derived_data;
public:
    void base1_func() override {}
    void base2_func() override {}
};
static_assert(sizeof(Base1) == 16);
static_assert(sizeof(Base2) == 16);
static_assert(sizeof(MultiDerived) == 32);

/*
MultiDerived 内存布局:
[Base1部分]
[vptr1(8字节)][base1_data(4字节)][填充(4字节)]
01234567       89AB               CDEF

[Base2部分]
[vptr2(8字节)][base2_data(4字节)][derived_data(4字节)]
GHIJKLMN       OPQR               STUV

说明:
- 每个基类都有自己的虚函数表指针
- 内存中按照继承顺序排列基类子对象
- 派生类成员放在最后一个基类部分
*/

位域 Bit Fields

位域允许指定结构体成员的精确位数,实现位级内存控制和数据紧凑存储。

  1. 位级精度:支持 1-63 位指定(依据基础类型)
  2. 自动打包:编译器在同一存储单元中紧密排列
  3. 类型约束:仅支持整数类型(int、char、short、long等)
cpp 复制代码
#include <cstddef>

struct NormalStruct {
    int a;
    int b;
    int c;
};
static_assert(sizeof(NormalStruct) == 12);
static_assert(alignof(NormalStruct) == 4);
static_assert(offsetof(NormalStruct, a) == 0);
static_assert(offsetof(NormalStruct, b) == 4);
static_assert(offsetof(NormalStruct, c) == 8);
NormalStruct n {.a = 1, .b = 2, .c = 100};

struct BitFieldStruct {
    int a         : 1;  // 只用 1 位
    unsigned int b: 1;  // 只用 1 位
    int c         : 6;  // 只用 6 位
};
static_assert(sizeof(BitFieldStruct) == 4);   // 所有位域打包在 1 个 int 中
static_assert(alignof(BitFieldStruct) == 4);  // 对齐要求不变,仍然是 int 的对齐
// static_assert(offsetof(BitFieldStruct, a) == 0); // Cannot compute offset of bit-field 'a'

BitFieldStruct b {.a = -1, .b = 1, .c = 10};

bool 类型虽然只表示真/假两个状态,但通常占用完整的1字节。位域可以将多个 bool 值压缩到单一存储单元中,显著减少内存占用而不影响功能。

cpp 复制代码
struct NormalBoolFlags {
    bool flag1;  // 占用 1 字节
    bool flag2;  // 占用 1 字节
    bool flag3;  // 占用 1 字节
    bool flag4;  // 占用 1 字节
};
static_assert(sizeof(NormalBoolFlags) == 4);

struct CompactBoolFlags {
    bool flag1: 1;  // 只占用 1 位
    bool flag2: 1;  // 只占用 1 位
    bool flag3: 1;  // 只占用 1 位
    bool flag4: 1;  // 只占用 1 位
    bool flag5: 1;  // 只占用 1 位
    bool flag6: 1;  // 只占用 1 位
    bool flag7: 1;  // 只占用 1 位
    bool flag8: 1;  // 只占用 1 位
};
static_assert(sizeof(CompactBoolFlags) == 1);

特殊内存布局

空类优化(Empty Base Optimization)

cpp 复制代码
class Empty {};  // 空类

class NotEmpty {
    int data;
};

class InheritEmpty : public Empty {
    int data;
};

static_assert(sizeof(Empty) == 1);          // 空类最小为 1 字节
static_assert(sizeof(NotEmpty) == 4);       // 只有 int 成员
static_assert(sizeof(InheritEmpty) == 4);   // EBO:空基类不占空间

/*
Empty Base Optimization (EBO):
- 空基类不会占用派生类的存储空间
- 但独立的空类对象必须占用至少 1 字节(保证唯一地址)
*/

成员指针的内存布局

cpp 复制代码
class TestClass {
public:
    int member_var;
    virtual void virtual_func() {}
    void normal_func() {}
};

int main() {
    // 数据成员指针
    int TestClass::*data_ptr = &TestClass::member_var;
    
    // 普通成员函数指针  
    void (TestClass::*normal_ptr)() = &TestClass::normal_func;
    
    // 虚成员函数指针
    void (TestClass::*virtual_ptr)() = &TestClass::virtual_func;
    
    printf("数据成员指针大小: %zu\n", sizeof(data_ptr));
    printf("普通函数指针大小: %zu\n", sizeof(normal_ptr));
    printf("虚函数指针大小: %zu\n", sizeof(virtual_ptr));

    return 0;
}

/*
典型输出:
数据成员指针大小: 8    (偏移量)
普通函数指针大小: 16   (函数地址 + 调整值)
虚函数指针大小: 16     (vtable偏移 + 调整值)

成员函数指针更大的原因:
- 支持多重继承的 this 指针调整
- 处理虚函数的 vtable 索引
*/

联合体(Union)共享内存空间

cpp 复制代码
// 基础联合体
union BasicUnion {
    int int_value;      // 4 字节
    float float_value;  // 4 字节
    char bytes[4];      // 4 字节
};


static_assert(sizeof(BasicUnion) == 4);
static_assert(alignof(BasicUnion) == 4);
static_assert(offsetof(BasicUnion, int_value) == 0);
static_assert(offsetof(BasicUnion, float_value) == 0);
static_assert(offsetof(BasicUnion, bytes) == 0);
相关推荐
默|笙3 小时前
【c++】STL-容器 list 的实现
c++·windows·list
屁股割了还要学3 小时前
【C语言进阶】题目练习(2)
c语言·开发语言·c++·学习·算法·青少年编程
weixin_457665394 小时前
基于可变参模板的线程池
linux·c++
Mr_Xuhhh4 小时前
Qt窗口(2)-工具栏
java·c语言·开发语言·数据库·c++·qt·算法
kyle~6 小时前
C++---cout、cerr、clog
开发语言·c++·算法
HHRL-yx6 小时前
C++网络编程 4.UDP套接字(socket)编程示例程序
网络·c++·udp
tan77º7 小时前
【Linux网络编程】应用层协议 - HTTP
linux·服务器·网络·c++·http·https·tcp
alicema11117 小时前
萤石摄像头C++SDK应用实例
开发语言·前端·c++·qt·opencv
The Chosen One9857 小时前
C++ :vector的介绍和使用
开发语言·c++
Aurora_wmroy7 小时前
算法竞赛备赛——【图论】求最短路径——Floyd算法
数据结构·c++·算法·蓝桥杯·图论