深入理解 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-63 位指定(依据基础类型)
- 自动打包:编译器在同一存储单元中紧密排列
- 类型约束:仅支持整数类型(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);