第9章 数组和指针

数组

数组的基本概念

数组是一种数据结构,用于存储相同类型的多个元素。在C++中,数组的大小在声明时必须确定,且在程序运行过程中不能改变。数组的核心特性是连续内存布局固定大小,这使得数组具有高效的随机访问性能。

数组的底层实现

数组在内存中以连续的方式存储元素,这意味着:

  1. 内存连续性:数组元素在内存中是连续存储的,相邻元素之间没有间隙
  2. 元素访问:通过基地址加上偏移量计算元素地址,实现O(1)时间复杂度的随机访问
  3. 内存布局:数组的内存布局由元素类型和数组大小决定
1
2
3
4
5
6
7
8
9
// 数组的内存布局示例
int arr[5] = {10, 20, 30, 40, 50};
// 在内存中的布局(假设int为4字节):
// 地址: 0x1000 0x1004 0x1008 0x100C 0x1010
// 值: 10 20 30 40 50

// 元素访问的底层计算
// arr[i] 等价于 *(arr + i)
// 地址计算: arr + i * sizeof(int)

数组的内存对齐与硬件映射

数组元素的内存对齐对于性能至关重要,它直接影响CPU缓存命中率和内存访问效率:

1
2
3
4
5
6
7
8
9
10
11
// 内存对齐示例
struct alignas(16) AlignedType {
int x; // 4字节
double y; // 8字节
};

// 数组元素会按照类型的对齐要求进行对齐
AlignedType arr[2];
// AlignedType的大小为16字节(由于alignas(16))
// arr[0] 从16字节对齐地址开始
// arr[1] 从32字节对齐地址开始

内存对齐的底层原理

  1. 硬件约束:现代CPU对内存访问有对齐要求,未对齐的访问会导致性能下降甚至硬件异常
  2. 缓存行优化:对齐的数据能更好地利用CPU缓存行(通常为64字节)
  3. SIMD指令:SIMD指令要求数据必须对齐到特定边界(如16字节或32字节)
1
2
3
4
5
6
// 缓存友好的数组布局
struct alignas(64) CacheLineAligned {
double data[8]; // 8个double正好填满64字节缓存行
};

CacheLineAligned arr[4]; // 每个元素都对齐到64字节边界

汇编级内存对齐分析

1
2
3
4
5
6
7
8
9
; x86-64汇编示例:对齐内存访问
mov rax, arr ; 加载数组基地址
movaps xmm0, [rax] ; 对齐的SIMD加载(要求16字节对齐)
movups xmm1, [rax+8] ; 未对齐的SIMD加载(性能较慢)

; 数组元素访问的地址计算
; arr[i] 的地址计算
lea rdx, [rax + rdi*8] ; rdi = i, 8 = sizeof(double)
movsd xmm0, [rdx] ; 加载double元素

内存对齐优化策略

  1. 使用alignas指定对齐要求:显式控制内存对齐
  2. 避免跨越缓存行:设计数据结构时考虑缓存行边界
  3. 内存分配对齐:使用aligned_alloc或std::aligned_alloc分配对齐内存
  4. 编译选项优化:使用-falign-arrays等编译器选项
1
2
3
4
5
6
7
// 使用std::aligned_alloc分配对齐内存
void* alignedMem = std::aligned_alloc(64, 1024);
if (alignedMem) {
double* arr = static_cast<double*>(alignedMem);
// 使用对齐数组
std::free(alignedMem);
}

数组的类型特性

数组在C++中具有独特的类型特性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 数组的类型特性
int arr[5];

// 数组类型包含大小信息
static_assert(std::is_same_v<decltype(arr), int[5]>);

// 数组到指针的隐式转换(衰减)
const int* ptr = arr; // arr衰减为指向第一个元素的指针

// 引用数组(保持数组类型信息)
int (&arrRef)[5] = arr;
static_assert(std::is_same_v<decltype(arrRef), int(&)[5]>);

// 数组大小计算
constexpr size_t size = sizeof(arr) / sizeof(arr[0]);
static_assert(size == 5);

// 数组类型作为模板参数
template <typename T>
void printType() {
std::cout << typeid(T).name() << std::endl;
}

printType<decltype(arr)>(); // 输出数组类型

数组的声明和初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#include <iostream>

int main() {
// 声明一个包含5个整数的数组
int numbers[5];

// 初始化数组元素
numbers[0] = 10;
numbers[1] = 20;
numbers[2] = 30;
numbers[3] = 40;
numbers[4] = 50;

// 声明并初始化数组
int scores[3] = {95, 87, 91}; // 初始化前3个元素,其余为0

// 声明并初始化所有元素
double prices[] = {19.99, 29.99, 39.99, 49.99}; // 编译器自动计算大小

// 字符数组(字符串)
char message[] = "Hello, World!";

// 遍历数组
std::cout << "Numbers: ";
for (int i = 0; i < 5; i++) {
std::cout << numbers[i] << " ";
}
std::cout << std::endl;

// 使用范围for循环(C++11+)
std::cout << "Scores: ";
for (int score : scores) {
std::cout << score << " ";
}
std::cout << std::endl;

return 0;
}

数组的特性

  • 连续存储:数组元素在内存中是连续存储的
  • 固定大小:数组大小在声明时确定,不能动态改变
  • 索引访问:使用下标(从0开始)访问数组元素
  • 边界检查:C++不自动检查数组边界,需要程序员自己确保不越界

多维数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include <iostream>

int main() {
// 声明一个2x3的二维数组
int matrix[2][3] = {
{1, 2, 3},
{4, 5, 6}
};

// 访问二维数组元素
std::cout << "matrix[0][0]: " << matrix[0][0] << std::endl;
std::cout << "matrix[1][2]: " << matrix[1][2] << std::endl;

// 遍历二维数组
std::cout << "Matrix: " << std::endl;
for (int i = 0; i < 2; i++) {
for (int j = 0; j < 3; j++) {
std::cout << matrix[i][j] << " ";
}
std::cout << std::endl;
}

// 三维数组
int cube[2][2][2] = {
{{1, 2}, {3, 4}},
{{5, 6}, {7, 8}}
};

return 0;
}

指针

指针的基本概念

指针是一种变量,用于存储内存地址。通过指针,可以间接访问和修改内存中的数据。指针的核心价值在于提供了直接内存操作能力高效的参数传递方式

指针的底层实现

指针在内存中存储的是另一个变量的内存地址:

  1. 指针大小:指针的大小取决于目标平台的地址空间大小(32位系统为4字节,64位系统为8字节)
  2. 指针类型:指针的类型决定了指针算术的步长和对指向内存的解释方式
  3. 内存布局:指针变量本身在内存中占用固定大小的空间,存储的是目标变量的地址
1
2
3
4
5
6
7
8
9
10
11
12
// 指针的内存布局示例
int x = 100; // 假设存储在地址0x1000
int* ptr = &x; // ptr存储0x1000,自身存储在地址0x2000

// 在内存中的布局:
// 地址0x1000: 100 (x的值)
// 地址0x2000: 0x1000 (ptr的值,即x的地址)

// 指针的大小
std::cout << "Size of int*: " << sizeof(int*) << " bytes" << std::endl;
std::cout << "Size of double*: " << sizeof(double*) << " bytes" << std::endl;
std::cout << "Size of void*: " << sizeof(void*) << " bytes" << std::endl;

指针类型的重要性

指针的类型决定了:

  1. 解引用的行为:如何解释指向的内存内容
  2. 指针算术的步长:指针加减操作的字节数
  3. 类型安全:不同类型的指针之间的转换需要显式进行
1
2
3
4
5
6
7
8
9
10
11
12
// 指针类型对算术运算的影响
int arr[5] = {1, 2, 3, 4, 5};
int* intPtr = arr;
double* doublePtr = reinterpret_cast<double*>(arr);

std::cout << "intPtr: " << intPtr << ", *intPtr: " << *intPtr << std::endl;
intPtr++; // 移动4字节(sizeof(int))
std::cout << "intPtr++: " << intPtr << ", *intPtr: " << *intPtr << std::endl;

std::cout << "doublePtr: " << doublePtr << ", *doublePtr: " << *doublePtr << std::endl;
doublePtr++; // 移动8字节(sizeof(double))
std::cout << "doublePtr++: " << doublePtr << ", *doublePtr: " << *doublePtr << std::endl;

指针的内存对齐

指针的对齐要求取决于其指向的数据类型:

1
2
3
4
5
6
7
8
9
10
11
12
// 指针的对齐要求
struct alignas(16) AlignedData {
int x;
double y;
};

AlignedData data;
AlignedData* ptr = &data;

std::cout << "Address of data: " << &data << std::endl;
std::cout << "Alignment requirement of AlignedData: " << alignof(AlignedData) << " bytes" << std::endl;
std::cout << "Is ptr aligned? " << ((reinterpret_cast<uintptr_t>(ptr) % alignof(AlignedData)) == 0) << std::endl;

指针的声明和初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <iostream>

int main() {
// 声明一个整数变量
int x = 100;

// 声明一个指向整数的指针,并初始化为x的地址
int* ptr = &x;

// 输出变量的值和地址
std::cout << "x = " << x << std::endl;
std::cout << "&x = " << &x << std::endl;
std::cout << "ptr = " << ptr << std::endl;
std::cout << "*ptr = " << *ptr << std::endl; // 解引用指针,获取值

// 通过指针修改变量的值
*ptr = 200;
std::cout << "After modification: " << std::endl;
std::cout << "x = " << x << std::endl;
std::cout << "*ptr = " << *ptr << std::endl;

// 空指针
int* nullPtr = nullptr; // C++11+推荐使用nullptr
int* oldNullPtr = NULL; // 传统方式

return 0;
}

指针的运算

指针算术是C++中强大的特性,它允许指针根据所指向类型的大小进行算术运算。指针算术的底层实现依赖于指针的类型信息,这使得指针能够以正确的步长在内存中移动,是实现高效数组操作和内存管理的基础。

指针算术的底层实现

指针算术的核心是按类型大小计算偏移量,由编译器在编译期完成地址计算:

  1. 指针加法ptr + n 等价于 reinterpret_cast<char*>(ptr) + n * sizeof(*ptr)
  2. 指针减法ptr - n 等价于 reinterpret_cast<char*>(ptr) - n * sizeof(*ptr)
  3. 指针比较:基于指针在内存中的实际地址进行比较
  4. 指针差值ptr2 - ptr1 计算元素个数,结果类型为 ptrdiff_t
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 指针算术的底层实现示例
int arr[5] = {10, 20, 30, 40, 50};
int* ptr = arr;

// 指针加法的底层计算
std::cout << "ptr = " << static_cast<void*>(ptr) << std::endl;
ptr++; // 等价于 ptr = reinterpret_cast<int*>(reinterpret_cast<char*>(ptr) + sizeof(int))
std::cout << "ptr++ = " << static_cast<void*>(ptr) << std::endl;

// 指针减法
ptr = arr + 4; // 指向最后一个元素
std::cout << "ptr = " << static_cast<void*>(ptr) << std::endl;
ptr--; // 等价于 ptr = reinterpret_cast<int*>(reinterpret_cast<char*>(ptr) - sizeof(int))
std::cout << "ptr-- = " << static_cast<void*>(ptr) << std::endl;

// 指针之间的减法
int* start = arr;
int* end = arr + 5;
ptrdiff_t elements = end - start;
std::cout << "Elements between start and end: " << elements << std::endl;

汇编级指针算术分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
; x86-64汇编示例:指针算术
section .data
arr dq 10, 20, 30, 40, 50 ; 64位整数数组

section .text
global main
main:
; 加载数组基地址
lea rax, [arr]

; 指针加法:ptr + 2
mov rdi, 2
lea rdx, [rax + rdi*8] ; 8 = sizeof(int64_t)

; 指针减法:ptr - 1
sub rdx, 8 ; 等价于 rdx--

; 指针比较:ptr1 < ptr2
cmp rax, rdx
jl less_than

; 指针差值计算
sub rdx, rax
shr rdx, 3 ; 除以8得到元素个数

less_than:
ret

指针算术的SIMD优化

现代编译器会利用SIMD指令优化指针算术操作,特别是在数组遍历和数值计算中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// SIMD优化的数组遍历示例
#include <immintrin.h>

void vectorizedArrayAdd(const float* a, const float* b, float* result, size_t size) {
size_t i = 0;

// 使用SIMD指令处理16个float元素(512位向量)
for (; i + 15 < size; i += 16) {
__m512 va = _mm512_loadu_ps(&a[i]);
__m512 vb = _mm512_loadu_ps(&b[i]);
__m512 vres = _mm512_add_ps(va, vb);
_mm512_storeu_ps(&result[i], vres);
}

// 处理剩余元素
for (; i < size; i++) {
result[i] = a[i] + b[i];
}
}

指针算术的高级应用

  1. 内存块管理:使用指针算术在动态分配的内存块中导航
  2. 数据结构实现:链表、树等数据结构的节点遍历
  3. 字符串处理:C风格字符串的高效操作
  4. 类型 punning:通过指针类型转换实现不同类型间的转换
  5. 内存对齐检查:使用指针算术验证内存对齐
1
2
3
4
5
6
7
8
9
10
11
12
// 内存对齐检查函数
template <typename T>
bool isAligned(const T* ptr, size_t alignment) {
return (reinterpret_cast<uintptr_t>(ptr) % alignment) == 0;
}

// 类型punning示例
float intToFloat(int value) {
int* iptr = &value;
float* fptr = reinterpret_cast<float*>(iptr);
return *fptr;
}

指针算术的性能考量

  1. 编译期优化:编译器会将指针算术转换为高效的地址计算
  2. 缓存局部性:顺序指针访问具有良好的缓存局部性
  3. 分支预测:线性指针算术有助于分支预测
  4. SIMD友好:连续的指针访问适合SIMD向量化
  5. 边界检查:指针算术需要手动确保不越界
1
2
3
4
5
6
7
8
9
10
11
12
// 缓存友好的指针访问模式
void processArray(const int* arr, size_t size) {
// 顺序访问:缓存友好
for (const int* p = arr; p < arr + size; p++) {
// 处理 *p
}

// 随机访问:缓存不友好
for (size_t i = 0; i < size; i += 100) {
// 处理 arr[i]
}
}

高级指针操作

1. 指针与数组的关系深入

数组名在大多数情况下会衰减为指向第一个元素的指针,但有一些例外情况:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 数组名衰减的例外情况
int arr[5] = {1, 2, 3, 4, 5};

// 1. sizeof运算符
std::cout << "sizeof(arr): " << sizeof(arr) << std::endl; // 输出20(5*4),不是指针大小

// 2. 取地址运算符
int (*ptrToArr)[5] = &arr; // 指向整个数组的指针
std::cout << "*ptrToArr: " << *ptrToArr << std::endl; // 输出数组第一个元素的地址
std::cout << "(**ptrToArr): " << **ptrToArr << std::endl; // 输出第一个元素的值

// 3. 引用初始化
int (&arrRef)[5] = arr; // 数组引用,保持数组类型信息

// 4. 模板参数
template <typename T>
void foo(T t) {
std::cout << typeid(T).name() << std::endl;
}

foo(arr); // 传递数组类型,不是指针
2. 指向多维数组的指针

多维数组的指针操作需要理解数组的内存布局:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 指向多维数组的指针
int matrix[2][3] = {{1, 2, 3}, {4, 5, 6}};

// 指向第一个元素的指针(int*)
int* elemPtr = &matrix[0][0];

// 指向第一行的指针(int(*)[3])
int (*rowPtr)[3] = matrix;

// 遍历多维数组
std::cout << "Using elemPtr: " << std::endl;
for (int i = 0; i < 2; i++) {
for (int j = 0; j < 3; j++) {
std::cout << *(elemPtr + i * 3 + j) << " ";
}
std::cout << std::endl;
}

std::cout << "Using rowPtr: " << std::endl;
for (int i = 0; i < 2; i++) {
for (int j = 0; j < 3; j++) {
std::cout << rowPtr[i][j] << " ";
}
std::cout << std::endl;
}
3. 函数指针和指针函数

函数指针是指向函数的指针,而指针函数是返回指针的函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// 函数指针和指针函数

// 普通函数
int add(int a, int b) {
return a + b;
}

// 函数指针类型
using AddFunction = int (*)(int, int);

// 指针函数
int* createInt(int value) {
return new int(value);
}

int main() {
// 使用函数指针
AddFunction funcPtr = add;
int result = funcPtr(5, 3);
std::cout << "5 + 3 = " << result << std::endl;

// 使用指针函数
int* ptr = createInt(42);
std::cout << "*ptr = " << *ptr << std::endl;
delete ptr;

return 0;
}
4. 成员指针

成员指针用于指向类的成员变量或成员函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// 成员指针
class Person {
public:
std::string name;
int age;

Person(std::string n, int a) : name(n), age(a) {}

void greet() {
std::cout << "Hello, my name is " << name << std::endl;
}
};

int main() {
Person p("Alice", 30);

// 指向成员变量的指针
std::string Person::*namePtr = &Person::name;
int Person::*agePtr = &Person::age;

std::cout << "Name: " << p.*namePtr << std::endl;
std::cout << "Age: " << p.*agePtr << std::endl;

// 指向成员函数的指针
void (Person::*greetPtr)() = &Person::greet;
(p.*greetPtr)(); // 调用成员函数

return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <iostream>

int main() {
int numbers[] = {10, 20, 30, 40, 50};
int* ptr = numbers; // 指向数组第一个元素

// 指针算术运算
std::cout << "*ptr = " << *ptr << std::endl; // 10
ptr++; // 指针向后移动一个元素(移动sizeof(int)字节)
std::cout << "*ptr = " << *ptr << std::endl; // 20
ptr += 2; // 指针向后移动两个元素
std::cout << "*ptr = " << *ptr << std::endl; // 40
ptr--; // 指针向前移动一个元素
std::cout << "*ptr = " << *ptr << std::endl; // 30

// 指针比较
int* start = numbers;
int* end = numbers + 5; // 指向数组末尾的下一个位置

std::cout << "Array elements: ";
while (start < end) {
std::cout << *start << " ";
start++;
}
std::cout << std::endl;

return 0;
}

指针算术的规则

  1. 指针与整数的加减

    • ptr + n:指针向后移动n个元素,实际移动的字节数为 n * sizeof(*ptr)
    • ptr - n:指针向前移动n个元素,实际移动的字节数为 n * sizeof(*ptr)
    • ptr++/++ptr:指针向后移动1个元素
    • ptr--/--ptr:指针向前移动1个元素
  2. 指针之间的减法

    • ptr2 - ptr1:计算两个指针之间的元素个数,结果为整数类型(ptrdiff_t)
    • 要求两个指针指向同一个数组或同一个对象的不同部分
  3. 指针比较运算

    • ==!=:判断两个指针是否指向同一位置
    • <<=>>=:判断指针在内存中的相对位置
    • 要求两个指针指向同一个数组或同一个对象的不同部分

指针算术的应用场景

  1. 数组遍历:使用指针算术遍历数组元素
  2. 动态内存管理:在动态分配的内存块中导航
  3. 字符串处理:操作C风格字符串
  4. 数据结构实现:实现链表、树等数据结构
  5. 性能优化:指针算术通常比数组下标访问更快

指针算术的注意事项

  1. 越界访问:指针算术可能导致越界访问,应确保指针始终在有效范围内
  2. 类型安全:指针算术依赖于指针的类型,不同类型的指针算术结果不同
  3. 空指针:对空指针进行算术运算会导致未定义行为
  4. 野指针:对野指针进行算术运算会导致未定义行为
  5. void指针:void指针不支持算术运算,因为它的大小未知

指针算术示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <iostream>

int main() {
double values[] = {1.1, 2.2, 3.3, 4.4, 5.5};
double* p = values;

// 指针与整数相加
std::cout << "p = " << p << ", *p = " << *p << std::endl;
p += 2; // 移动2个double元素,即16字节(假设double为8字节)
std::cout << "p += 2: " << p << ", *p = " << *p << std::endl;

// 指针之间的减法
double* start = values;
double* end = values + 5;
ptrdiff_t count = end - start;
std::cout << "Elements count: " << count << std::endl;

// 指针比较
if (start < end) {
std::cout << "start is before end" << std::endl;
}

return 0;
}

指针与数组的关系

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <iostream>

int main() {
int numbers[] = {10, 20, 30, 40, 50};

// 数组名是指向第一个元素的常量指针
std::cout << "*numbers = " << *numbers << std::endl; // 10
std::cout << "*(numbers + 1) = " << *(numbers + 1) << std::endl; // 20

// 指针可以像数组一样使用下标
int* ptr = numbers;
std::cout << "ptr[0] = " << ptr[0] << std::endl; // 10
std::cout << "ptr[2] = " << ptr[2] << std::endl; // 30

// 数组可以像指针一样进行算术运算
std::cout << "*(numbers + 3) = " << *(numbers + 3) << std::endl; // 40

return 0;
}

指针与函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include <iostream>

// 指针作为函数参数(传地址)
void increment(int* value) {
(*value)++;
}

// 函数返回指针
int* createArray(int size) {
int* arr = new int[size];
for (int i = 0; i < size; i++) {
arr[i] = i * 10;
}
return arr;
}

int main() {
int x = 5;
std::cout << "Before: x = " << x << std::endl;
increment(&x);
std::cout << "After: x = " << x << std::endl;

// 使用函数返回的指针
int* numbers = createArray(5);
std::cout << "Array elements: ";
for (int i = 0; i < 5; i++) {
std::cout << numbers[i] << " ";
}
std::cout << std::endl;

// 释放动态分配的内存
delete[] numbers;

return 0;
}

指向指针的指针

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <iostream>

int main() {
int x = 100;
int* ptr = &x;
int** ptrToPtr = &ptr;

std::cout << "x = " << x << std::endl;
std::cout << "*ptr = " << *ptr << std::endl;
std::cout << "**ptrToPtr = " << **ptrToPtr << std::endl;

// 通过指向指针的指针修改值
**ptrToPtr = 200;
std::cout << "After modification: " << std::endl;
std::cout << "x = " << x << std::endl;
std::cout << "*ptr = " << *ptr << std::endl;
std::cout << "**ptrToPtr = " << **ptrToPtr << std::endl;

return 0;
}

动态内存分配

new 和 delete 运算符

动态内存分配是C++中管理内存的重要机制,允许程序在运行时根据需要分配和释放内存。newdelete运算符是C++中进行动态内存管理的基本工具,它们提供了类型安全的内存分配和对象生命周期管理。

动态内存分配的底层实现

new运算符的工作原理:

  1. 内存分配:调用operator new函数分配原始内存,该函数内部通常调用malloc
  2. 内存检查:检查分配是否成功,失败则抛出std::bad_alloc异常
  3. 对象构造:使用 placement new 在分配的内存上调用对象的构造函数
  4. 返回指针:返回指向构造好的对象的类型化指针

delete运算符的工作原理:

  1. 空指针检查:如果指针为nullptr,直接返回
  2. 对象析构:调用对象的析构函数,清理对象资源
  3. 内存释放:调用operator delete函数释放内存,该函数内部通常调用free
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// new和delete的底层实现示例
class Person {
public:
std::string name;
int age;

Person(std::string n, int a) : name(n), age(a) {
std::cout << "Person constructor called" << std::endl;
}

~Person() {
std::cout << "Person destructor called" << std::endl;
}
};

int main() {
// 底层过程:
// 1. 调用operator new分配内存
// 2. 调用Person构造函数
// 3. 返回Person*指针
Person* p = new Person("Alice", 30);

// 使用对象
std::cout << "Name: " << p->name << ", Age: " << p->age << std::endl;

// 底层过程:
// 1. 调用Person析构函数
// 2. 调用operator delete释放内存
delete p;

return 0;
}

内存分配器的底层实现

C++允许通过重载operator newoperator delete来自定义内存分配行为,这对于性能优化和特殊内存需求非常重要:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
// 自定义内存分配器实现
class MemoryPool {
private:
struct Block {
Block* next;
char data[1];
};

Block* freeList;
size_t blockSize;
std::mutex mutex;

public:
MemoryPool(size_t size) : blockSize(size), freeList(nullptr) {}

void* allocate(size_t size) {
std::lock_guard<std::mutex> lock(mutex);

if (freeList) {
// 从空闲列表中分配
void* result = freeList->data;
freeList = freeList->next;
return result;
}

// 分配新块
size_t allocSize = sizeof(Block) + blockSize - 1;
Block* newBlock = static_cast<Block*>(std::malloc(allocSize));
if (!newBlock) {
throw std::bad_alloc();
}

return newBlock->data;
}

void deallocate(void* ptr) {
std::lock_guard<std::mutex> lock(mutex);

// 将内存块放回空闲列表
Block* block = reinterpret_cast<Block*>(
static_cast<char*>(ptr) - offsetof(Block, data)
);
block->next = freeList;
freeList = block;
}
};

// 使用内存池的类
class PoolAllocated {
private:
static MemoryPool pool;

public:
int value;

PoolAllocated(int v) : value(v) {}

void* operator new(size_t size) {
return pool.allocate(size);
}

void operator delete(void* ptr) noexcept {
pool.deallocate(ptr);
}
};

MemoryPool PoolAllocated::pool(sizeof(PoolAllocated));

内存分配的性能优化

  1. 内存池技术:预分配内存块,减少系统调用开销
  2. 对齐优化:确保内存分配满足对齐要求,提高访问效率
  3. 批量分配:一次性分配多个对象的内存,减少分配次数
  4. 缓存友好:考虑内存局部性,减少缓存未命中
  5. 定制分配器:为特定类型设计专用分配器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
// 高性能内存池实现
template <typename T, size_t BlockSize = 4096>
class FastMemoryPool {
private:
union Slot {
T object;
Slot* next;
};

Slot* freeSlots;
std::vector<void*> blocks;

void allocateBlock() {
// 分配一个新的内存块
char* block = new char[BlockSize];
blocks.push_back(block);

// 将块分割为多个slot
size_t numSlots = (BlockSize - sizeof(Slot*)) / sizeof(Slot);
for (size_t i = 0; i < numSlots; i++) {
Slot* slot = reinterpret_cast<Slot*>(block + i * sizeof(Slot));
slot->next = freeSlots;
freeSlots = slot;
}
}

public:
FastMemoryPool() : freeSlots(nullptr) {}

~FastMemoryPool() {
for (void* block : blocks) {
delete[] static_cast<char*>(block);
}
}

T* allocate() {
if (!freeSlots) {
allocateBlock();
}

T* result = &freeSlots->object;
freeSlots = freeSlots->next;
return result;
}

void deallocate(T* ptr) {
Slot* slot = reinterpret_cast<Slot*>(ptr);
slot->next = freeSlots;
freeSlots = slot;
}
};

// 使用示例
template <typename T>
class PoolAllocator {
private:
FastMemoryPool<T> pool;

public:
using value_type = T;

PoolAllocator() = default;

template <typename U>
PoolAllocator(const PoolAllocator<U>&) {}

T* allocate(size_t n) {
if (n != 1) {
throw std::bad_alloc();
}
return pool.allocate();
}

void deallocate(T* ptr, size_t n) {
if (n != 1) {
return;
}
pool.deallocate(ptr);
}
};

// 使用自定义分配器
std::vector<int, PoolAllocator<int>> vec;

内存分配的安全考虑

  1. 内存泄漏:确保每个new都有对应的delete
  2. 双重释放:避免对同一指针多次调用delete
  3. 悬垂指针:避免使用已释放的内存
  4. 内存越界:确保在分配的内存范围内操作
  5. 异常安全:确保内存分配失败时不会导致资源泄漏
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 异常安全的内存分配示例
class Resource {
private:
int* data;

public:
Resource(size_t size) : data(nullptr) {
try {
data = new int[size];
// 其他可能抛出异常的初始化
} catch (...) {
delete[] data;
throw;
}
}

~Resource() {
delete[] data;
}

// 禁用复制和移动,简化示例
Resource(const Resource&) = delete;
Resource& operator=(const Resource&) = delete;
};

自定义内存分配器

C++允许自定义内存分配器,通过重载operator newoperator delete实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
// 自定义内存分配器
class CustomAllocated {
public:
int value;

CustomAllocated(int v) : value(v) {
std::cout << "CustomAllocated constructor" << std::endl;
}

~CustomAllocated() {
std::cout << "CustomAllocated destructor" << std::endl;
}

// 自定义operator new
void* operator new(size_t size) {
std::cout << "Custom operator new called, size: " << size << std::endl;
void* ptr = std::malloc(size);
if (!ptr) {
throw std::bad_alloc();
}
return ptr;
}

// 自定义operator delete
void operator delete(void* ptr) noexcept {
std::cout << "Custom operator delete called" << std::endl;
std::free(ptr);
}

// 数组版本
void* operator new[](size_t size) {
std::cout << "Custom operator new[] called, size: " << size << std::endl;
void* ptr = std::malloc(size);
if (!ptr) {
throw std::bad_alloc();
}
return ptr;
}

void operator delete[](void* ptr) noexcept {
std::cout << "Custom operator delete[] called" << std::endl;
std::free(ptr);
}
};

int main() {
// 使用自定义分配器
CustomAllocated* p1 = new CustomAllocated(42);
std::cout << "p1->value: " << p1->value << std::endl;
delete p1;

// 数组版本
CustomAllocated* pArray = new CustomAllocated[3]{1, 2, 3};
for (int i = 0; i < 3; i++) {
std::cout << "pArray[" << i << "].value: " << pArray[i].value << std::endl;
}
delete[] pArray;

return 0;
}

内存对齐和分配优化

内存对齐对于性能至关重要,C++17引入了std::aligned_alloc函数来分配对齐的内存:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// 内存对齐示例
#include <cstdlib>
#include <new>

int main() {
// 分配16字节对齐的内存
void* ptr = std::aligned_alloc(16, 1024);
if (ptr) {
std::cout << "Aligned pointer: " << ptr << std::endl;
std::cout << "Alignment check: " << (reinterpret_cast<uintptr_t>(ptr) % 16 == 0) << std::endl;

// 在对齐的内存上构造对象
int* intPtr = new (ptr) int[256]; // 放置new

// 使用内存
intPtr[0] = 42;
std::cout << "intPtr[0]: " << intPtr[0] << std::endl;

// 销毁对象
for (int i = 0; i < 256; i++) {
intPtr[i].~int();
}

// 释放内存
std::free(ptr);
}

return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#include <iostream>

int main() {
// 动态分配单个整数
int* pInt = new int;
*pInt = 42;
std::cout << "*pInt = " << *pInt << std::endl;
delete pInt; // 释放内存

// 动态分配数组
int size = 5;
int* pArray = new int[size];

// 初始化数组
for (int i = 0; i < size; i++) {
pArray[i] = i * 10;
}

// 输出数组元素
std::cout << "Array elements: ";
for (int i = 0; i < size; i++) {
std::cout << pArray[i] << " ";
}
std::cout << std::endl;

delete[] pArray; // 释放数组内存

// 动态分配对象
class Person {
public:
std::string name;
int age;

Person(std::string n, int a) : name(n), age(a) {
std::cout << "Person constructor called" << std::endl;
}

~Person() {
std::cout << "Person destructor called" << std::endl;
}

void display() {
std::cout << "Name: " << name << ", Age: " << age << std::endl;
}
};

Person* pPerson = new Person("Alice", 30);
pPerson->display();
delete pPerson;

return 0;
}

智能指针

智能指针是C++11引入的一种RAII(资源获取即初始化)机制,用于自动管理动态内存,避免内存泄漏。智能指针本质上是一个包装了裸指针的类,通过析构函数自动释放所管理的内存,同时提供了类型安全的指针操作接口。

智能指针的底层实现原理

智能指针的核心实现依赖于以下技术:

  1. RAII机制:在构造时获取资源,在析构时释放资源
  2. 模板技术:使用模板实现类型安全的指针包装
  3. 引用计数:shared_ptr使用引用计数追踪资源的所有者数量
  4. 移动语义:unique_ptr利用移动语义实现独占所有权的转移
  5. 类型擦除:支持自定义删除器而不影响智能指针的大小
  6. 原子操作:shared_ptr的引用计数操作是线程安全的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
// 简化的unique_ptr实现
template <typename T, typename Deleter = std::default_delete<T>>
class SimpleUniquePtr {
private:
T* ptr;
Deleter deleter;

public:
explicit SimpleUniquePtr(T* p = nullptr, const Deleter& d = Deleter())
: ptr(p), deleter(d) {}

~SimpleUniquePtr() {
if (ptr) {
deleter(ptr);
}
}

// 禁止复制
SimpleUniquePtr(const SimpleUniquePtr&) = delete;
SimpleUniquePtr& operator=(const SimpleUniquePtr&) = delete;

// 允许移动
SimpleUniquePtr(SimpleUniquePtr&& other) noexcept
: ptr(other.ptr), deleter(std::move(other.deleter)) {
other.ptr = nullptr;
}

SimpleUniquePtr& operator=(SimpleUniquePtr&& other) noexcept {
if (this != &other) {
if (ptr) {
deleter(ptr);
}
ptr = other.ptr;
deleter = std::move(other.deleter);
other.ptr = nullptr;
}
return *this;
}

// 指针操作
T* get() const { return ptr; }
T& operator*() const { return *ptr; }
T* operator->() const { return ptr; }
explicit operator bool() const { return ptr != nullptr; }

// 释放所有权
T* release() {
T* temp = ptr;
ptr = nullptr;
return temp;
}

// 重置指针
void reset(T* p = nullptr) {
if (ptr) {
deleter(ptr);
}
ptr = p;
}
};

// 简化的shared_ptr实现
template <typename T>
class SimpleSharedPtr {
private:
struct ControlBlock {
T* ptr;
std::atomic<int> refCount;
std::atomic<int> weakCount;

ControlBlock(T* p) : ptr(p), refCount(1), weakCount(0) {}

~ControlBlock() {
delete ptr;
}
};

ControlBlock* controlBlock;

void incrementRefCount() {
if (controlBlock) {
controlBlock->refCount.fetch_add(1, std::memory_order_relaxed);
}
}

void decrementRefCount() {
if (controlBlock) {
if (controlBlock->refCount.fetch_sub(1, std::memory_order_acq_rel) == 1) {
// 最后一个shared_ptr,销毁控制块
delete controlBlock;
controlBlock = nullptr;
}
}
}

public:
explicit SimpleSharedPtr(T* p = nullptr)
: controlBlock(p ? new ControlBlock(p) : nullptr) {}

SimpleSharedPtr(const SimpleSharedPtr& other)
: controlBlock(other.controlBlock) {
incrementRefCount();
}

SimpleSharedPtr& operator=(const SimpleSharedPtr& other) {
if (this != &other) {
decrementRefCount();
controlBlock = other.controlBlock;
incrementRefCount();
}
return *this;
}

~SimpleSharedPtr() {
decrementRefCount();
}

// 指针操作
T* get() const { return controlBlock ? controlBlock->ptr : nullptr; }
T& operator*() const { return *get(); }
T* operator->() const { return get(); }
explicit operator bool() const { return get() != nullptr; }
int use_count() const {
return controlBlock ? controlBlock->refCount.load(std::memory_order_acquire) : 0;
}
};

智能指针的性能优化

  1. 内存布局优化

    • unique_ptr:大小与裸指针相同(无额外开销)
    • shared_ptr:通常是两倍裸指针大小(存储指针和控制块指针)
    • make_shared:一次分配对象和控制块内存,减少内存分配次数
  2. 引用计数优化

    • 使用原子操作确保线程安全
    • 避免不必要的引用计数操作(使用移动语义)
    • 合理使用weak_ptr打破循环引用
  3. 删除器优化

    • 空基类优化(EBO)减少删除器的大小开销
    • 函数对象作为删除器比函数指针更高效
    • 小型删除器可以内联存储
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// 智能指针性能优化示例

// 1. 使用make_shared减少内存分配
auto p1 = std::make_shared<Person>("Alice", 30); // 一次内存分配

// 2. 使用移动语义避免引用计数操作
std::unique_ptr<Person> p2 = std::make_unique<Person>("Bob", 25);
std::unique_ptr<Person> p3 = std::move(p2); // 无引用计数操作

// 3. 空基类优化的删除器
struct EmptyBase { };

struct OptimizedDeleter : private EmptyBase {
void operator()(Person* p) const {
delete p;
}
};

// OptimizedDeleter的大小为1,但由于EBO,与Person*组合后大小不变
std::unique_ptr<Person, OptimizedDeleter> p4(new Person("Charlie", 35));

// 4. 自定义分配器的shared_ptr
struct CustomAllocator {
void* allocate(size_t size) {
return std::malloc(size);
}
void deallocate(void* ptr) {
std::free(ptr);
}
};

// 使用自定义分配器创建shared_ptr
CustomAllocator alloc;
Person* rawPtr = new Person("David", 40);
std::shared_ptr<Person> p5(rawPtr, [](Person* p) { delete p; });

智能指针的高级使用场景

  1. 自定义删除器的高级应用

    • 管理非内存资源(文件句柄、网络连接等)
    • 实现自定义资源释放逻辑
    • 与第三方库集成
  2. 智能指针与工厂模式

    • 返回智能指针的工厂函数
    • 类型擦除的对象工厂
    • 依赖注入容器
  3. 智能指针与线程安全

    • 线程间安全传递所有权
    • 共享状态的线程安全管理
    • 避免数据竞争
  4. 智能指针与多态

    • 基类指针管理派生类对象
    • 运行时多态的安全实现
    • 接口与实现分离
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
// 智能指针高级应用示例

// 1. 管理非内存资源
class FileGuard {
public:
explicit FileGuard(FILE* f) : file(f) {}
~FileGuard() {
if (file) {
fclose(file);
}
}

FILE* get() const { return file; }

private:
FILE* file;
};

// 使用unique_ptr管理文件
std::unique_ptr<FILE, decltype(&fclose)> filePtr(fopen("data.txt", "r"), &fclose);

// 2. 工厂函数返回智能指针
class Shape {
public:
virtual void draw() = 0;
virtual ~Shape() {}
};

class Circle : public Shape {
public:
void draw() override { /* 实现 */ }
};

class Square : public Shape {
public:
void draw() override { /* 实现 */ }
};

std::unique_ptr<Shape> createShape(const std::string& type) {
if (type == "circle") {
return std::make_unique<Circle>();
} else if (type == "square") {
return std::make_unique<Square>();
}
return nullptr;
}

// 3. 线程安全的智能指针使用
void processData(std::shared_ptr<Data> data) {
// 线程安全的操作
}

std::thread t1(processData, std::make_shared<Data>());
std::thread t2(processData, std::make_shared<Data>());

// 4. 多态与智能指针
std::vector<std::unique_ptr<Shape>> shapes;
shapes.push_back(std::make_unique<Circle>());
shapes.push_back(std::make_unique<Square>());

for (const auto& shape : shapes) {
shape->draw(); // 多态调用
}

智能指针的最佳实践

  1. 选择合适的智能指针

    • 默认使用unique_ptr,当需要共享所有权时使用shared_ptr
    • 避免不必要的shared_ptr,因为引用计数有开销
    • 使用weak_ptr打破循环引用
  2. 优先使用工厂函数

    • 使用make_unique和make_shared创建智能指针
    • 避免直接使用new初始化智能指针
    • 工厂函数提供更一致的错误处理
  3. 传递智能指针的正确方式

    • unique_ptr:使用移动语义传递
    • shared_ptr:根据需要传递值或const引用
    • 避免裸指针与智能指针混用
  4. 智能指针的性能考量

    • 注意shared_ptr的引用计数开销
    • 合理使用移动语义减少开销
    • 考虑自定义分配器优化内存使用
  5. 智能指针与异常安全

    • 智能指针确保即使发生异常也能释放资源
    • 避免在构造函数中使用裸指针分配内存
    • 使用RAII原则管理所有资源
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// 智能指针最佳实践示例

// 1. 优先使用make系列函数
auto p1 = std::make_unique<Person>("Alice", 30);
auto p2 = std::make_shared<Person>("Bob", 25);

// 2. 正确传递智能指针
void processUniquePtr(std::unique_ptr<Person> p) {
// 使用p
}

void processSharedPtr(const std::shared_ptr<Person>& p) {
// 使用p,无引用计数操作
}

// 3. 避免裸指针与智能指针混用
Person* createPerson() {
return new Person("Charlie", 35); // 不好的实践
}

std::unique_ptr<Person> createPersonSafe() {
return std::make_unique<Person>("Charlie", 35); // 好的实践
}

// 4. 异常安全的资源管理
class ResourceManager {
private:
std::unique_ptr<Resource> resource1;
std::unique_ptr<Resource> resource2;

public:
ResourceManager()
: resource1(std::make_unique<Resource>()),
resource2(std::make_unique<Resource>()) {
// 即使构造函数抛出异常,已分配的资源也会被释放
}
};

智能指针的种类

  1. std::unique_ptr:独占所有权的智能指针
  2. std::shared_ptr:共享所有权的智能指针
  3. std::weak_ptr:不增加引用计数的shared_ptr观察者
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#include <iostream>
#include <memory>

class Person {
public:
std::string name;
int age;

Person(std::string n, int a) : name(n), age(a) {
std::cout << "Person constructor called for " << name << std::endl;
}

~Person() {
std::cout << "Person destructor called for " << name << std::endl;
}

void display() {
std::cout << "Name: " << name << ", Age: " << age << std::endl;
}
};

int main() {
// 方法1:使用new初始化
std::unique_ptr<Person> p1(new Person("Alice", 30));
p1->display();

// 方法2:使用make_unique(C++14+,推荐)
auto p2 = std::make_unique<Person>("Bob", 25);
p2->display();

// 所有权转移
std::unique_ptr<Person> p3 = std::move(p1); // p1现在为空
if (p1) {
p1->display();
} else {
std::cout << "p1 is empty" << std::endl;
}
p3->display();

// 数组版本
auto pArray = std::make_unique<Person[]>(3);

return 0; // 智能指针自动释放内存
}

std::shared_ptr

std::shared_ptr是一种共享所有权的智能指针,多个shared_ptr可以指向同一个对象,通过引用计数管理对象的生命周期:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <iostream>
#include <memory>

int main() {
// 方法1:使用new初始化
std::shared_ptr<Person> p1(new Person("Charlie", 35));
std::cout << "Reference count: " << p1.use_count() << std::endl;

// 方法2:使用make_shared(推荐,更高效)
auto p2 = std::make_shared<Person>("David", 40);
std::cout << "Reference count: " << p2.use_count() << std::endl;

// 共享所有权
std::shared_ptr<Person> p3 = p2;
std::cout << "Reference count after copy: " << p2.use_count() << std::endl;

// 所有权转移
std::shared_ptr<Person> p4 = std::move(p1);
std::cout << "p1 use_count: " << p1.use_count() << std::endl;
std::cout << "p4 use_count: " << p4.use_count() << std::endl;

return 0; // 当引用计数为0时,对象自动释放
}

std::weak_ptr

std::weak_ptr是一种不增加引用计数的智能指针,用于观察shared_ptr管理的对象,避免循环引用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include <iostream>
#include <memory>

class Node {
public:
int value;
std::shared_ptr<Node> next;
std::weak_ptr<Node> prev; // 使用weak_ptr避免循环引用

Node(int v) : value(v) {
std::cout << "Node constructor called for " << value << std::endl;
}

~Node() {
std::cout << "Node destructor called for " << value << std::endl;
}
};

int main() {
auto n1 = std::make_shared<Node>(1);
auto n2 = std::make_shared<Node>(2);

n1->next = n2;
n2->prev = n1;

// 使用weak_ptr
std::weak_ptr<Node> wp = n1;

// 检查对象是否仍然存在
if (auto sp = wp.lock()) {
std::cout << "Node value: " << sp->value << std::endl;
} else {
std::cout << "Node no longer exists" << std::endl;
}

// 检查引用计数
std::cout << "n1 use_count: " << n1.use_count() << std::endl;
std::cout << "n2 use_count: " << n2.use_count() << std::endl;

return 0;
}

智能指针的高级特性

1. 自定义删除器

智能指针允许指定自定义删除器,用于特殊的资源管理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// 自定义删除器示例

// 函数式删除器
void customDeleter(Person* p) {
std::cout << "Custom deleter called for " << p->name << std::endl;
delete p;
}

// 函子删除器
struct Deleter {
void operator()(Person* p) const {
std::cout << "Functor deleter called for " << p->name << std::endl;
delete p;
}
};

int main() {
// 使用函数作为删除器
std::unique_ptr<Person, decltype(&customDeleter)> p1(new Person("Alice", 30), customDeleter);

// 使用函子作为删除器
std::unique_ptr<Person, Deleter> p2(new Person("Bob", 25));

// 使用lambda作为删除器
std::unique_ptr<Person, std::function<void(Person*)>> p3(
new Person("Charlie", 35),
[](Person* p) {
std::cout << "Lambda deleter called for " << p->name << std::endl;
delete p;
}
);

// shared_ptr也支持自定义删除器
std::shared_ptr<Person> p4(new Person("David", 40), customDeleter);

return 0;
}
2. 智能指针与数组

C++11及以后,智能指针支持管理数组:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// 智能指针管理数组
int main() {
// unique_ptr管理数组
std::unique_ptr<int[]> arr1(new int[5]{1, 2, 3, 4, 5});
for (int i = 0; i < 5; i++) {
std::cout << arr1[i] << " ";
}
std::cout << std::endl;

// 使用make_unique管理数组(C++14+)
auto arr2 = std::make_unique<int[]>(5);
for (int i = 0; i < 5; i++) {
arr2[i] = i * 2;
}
for (int i = 0; i < 5; i++) {
std::cout << arr2[i] << " ";
}
std::cout << std::endl;

// shared_ptr管理数组(需要自定义删除器)
std::shared_ptr<int> arr3(new int[5]{10, 20, 30, 40, 50}, [](int* p) { delete[] p; });
for (int i = 0; i < 5; i++) {
std::cout << arr3.get()[i] << " ";
}
std::cout << std::endl;

return 0;
}
3. 智能指针的性能优化

智能指针的性能优化策略:

  1. 优先使用make_unique和make_shared:减少内存分配次数,提高缓存一致性
  2. 合理使用移动语义:避免shared_ptr的引用计数操作
  3. 避免不必要的shared_ptr复制:使用const引用传递shared_ptr
  4. 注意weak_ptr的使用:避免循环引用,但也要注意lock()操作的开销
  5. 选择合适的智能指针:根据所有权模型选择unique_ptr或shared_ptr
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// 智能指针性能优化示例

// 1. 使用make_shared减少内存分配
auto p1 = std::make_shared<Person>("Alice", 30); // 一次内存分配

// 2. 使用移动语义
std::unique_ptr<Person> p2 = std::make_unique<Person>("Bob", 25);
std::unique_ptr<Person> p3 = std::move(p2); // 无内存操作

// 3. 避免不必要的shared_ptr复制
void processPerson(const std::shared_ptr<Person>& p) { // 常量引用传递
// 处理p
}

// 4. 合理使用weak_ptr
class Cache {
private:
std::unordered_map<std::string, std::weak_ptr<Person>> cache;
public:
void add(const std::string& key, const std::shared_ptr<Person>& value) {
cache[key] = value;
}

std::shared_ptr<Person> get(const std::string& key) {
auto it = cache.find(key);
if (it != cache.end()) {
return it->second.lock(); // 只在需要时获取shared_ptr
}
return nullptr;
}
};
4. 智能指针的线程安全性

智能指针的线程安全性:

  1. std::shared_ptr

    • 引用计数操作是线程安全的(原子操作)
    • 但指向的对象本身不是线程安全的
  2. std::unique_ptr

    • 不涉及共享状态,线程安全
    • 但同一unique_ptr的移动操作不能在多线程中并发进行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// 智能指针的线程安全示例
#include <thread>
#include <vector>

void incrementRefCount(std::shared_ptr<Person>& p) {
// 引用计数的增加是线程安全的
std::shared_ptr<Person> localCopy = p;
// 但对对象的访问需要同步
std::lock_guard<std::mutex> lock(mutex);
localCopy->age++;
}

int main() {
auto p = std::make_shared<Person>("Alice", 30);
std::vector<std::thread> threads;

for (int i = 0; i < 10; i++) {
threads.emplace_back(incrementRefCount, std::ref(p));
}

for (auto& t : threads) {
t.join();
}

std::cout << "Final age: " << p->age << std::endl;
std::cout << "Reference count: " << p.use_count() << std::endl;

return 0;
}

智能指针的原理

  1. RAII机制:智能指针在构造时获取资源,在析构时释放资源
  2. 所有权管理
    • unique_ptr:独占所有权,不允许复制,只允许移动
    • shared_ptr:共享所有权,通过引用计数追踪所有者数量
    • weak_ptr:不拥有所有权,只观察shared_ptr管理的对象
  3. 析构函数:智能指针的析构函数自动调用delete或delete[]释放内存

智能指针的使用场景

  1. std::unique_ptr

    • 独占资源的场景
    • 作为函数返回值
    • 作为类成员变量
    • 管理局部动态对象
  2. std::shared_ptr

    • 多个所有者共享资源的场景
    • 跨作用域共享对象
    • 复杂数据结构(如链表、树)中的节点
    • 长时间存在的对象
  3. std::weak_ptr

    • 打破shared_ptr的循环引用
    • 观察对象而不延长其生命周期
    • 缓存场景

智能指针的最佳实践

  1. 优先使用make_unique和make_shared

    • 更安全:避免内存泄漏(如果在构造过程中抛出异常)
    • 更高效:make_shared只分配一次内存(对象和控制块)
  2. 选择合适的智能指针

    • 默认使用unique_ptr,当需要共享所有权时使用shared_ptr
    • 避免不必要的shared_ptr,因为引用计数有开销
  3. 避免裸指针与智能指针混用

    • 不要用智能指针管理已经由裸指针管理的内存
    • 不要从智能指针获取裸指针后手动释放
  4. 注意循环引用

    • 在双向引用的场景中,使用weak_ptr打破循环
  5. 正确处理数组

    • unique_ptr支持数组版本:std::unique_ptr<T[]>
    • shared_ptr需要自定义删除器来管理数组
  6. 传递智能指针

    • 对于unique_ptr,使用移动语义传递
    • 对于shared_ptr,根据需要传递值或引用

智能指针的性能考量

  1. 内存开销

    • unique_ptr:几乎无额外开销,与裸指针大小相同
    • shared_ptr:有额外开销(控制块、引用计数)
    • weak_ptr:与shared_ptr类似,需要访问控制块
  2. 时间开销

    • unique_ptr:操作几乎无开销
    • shared_ptr:引用计数的增减需要原子操作,有一定开销
    • make_shared:比直接使用new更高效
  3. 优化策略

    • 优先使用unique_ptr
    • 合理使用make_shared减少内存分配
    • 避免频繁的shared_ptr复制
    • 注意移动语义的使用

std::unique_ptr

std::unique_ptr是一种独占所有权的智能指针,同一时间只能有一个unique_ptr指向同一个对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#include <iostream>
#include <memory>

class Person {
public:
std::string name;
int age;

Person(std::string n, int a) : name(n), age(a) {
std::cout << "Person constructor called for " << name << std::endl;
}

~Person() {
std::cout << "Person destructor called for " << name << std::endl;
}

void display() {
std::cout << "Name: " << name << ", Age: " << age << std::endl;
}
};

int main() {
// 方法1:使用new初始化
std::unique_ptr<Person> p1(new Person("Alice", 30));
p1->display();

// 方法2:使用make_unique(C++14+,推荐)
auto p2 = std::make_unique<Person>("Bob", 25);
p2->display();

// 所有权转移
std::unique_ptr<Person> p3 = std::move(p1); // p1现在为空
if (p1) {
p1->display();
} else {
std::cout << "p1 is empty" << std::endl;
}
p3->display();

// 数组版本
auto pArray = std::make_unique<Person[]>(3);

return 0; // 智能指针自动释放内存
}

std::shared_ptr

std::shared_ptr是一种共享所有权的智能指针,多个shared_ptr可以指向同一个对象,通过引用计数管理对象的生命周期:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <iostream>
#include <memory>

int main() {
// 方法1:使用new初始化
std::shared_ptr<Person> p1(new Person("Charlie", 35));
std::cout << "Reference count: " << p1.use_count() << std::endl;

// 方法2:使用make_shared(推荐,更高效)
auto p2 = std::make_shared<Person>("David", 40);
std::cout << "Reference count: " << p2.use_count() << std::endl;

// 共享所有权
std::shared_ptr<Person> p3 = p2;
std::cout << "Reference count after copy: " << p2.use_count() << std::endl;

// 所有权转移
std::shared_ptr<Person> p4 = std::move(p1);
std::cout << "p1 use_count: " << p1.use_count() << std::endl;
std::cout << "p4 use_count: " << p4.use_count() << std::endl;

return 0; // 当引用计数为0时,对象自动释放
}

std::weak_ptr

std::weak_ptr是一种不增加引用计数的智能指针,用于观察shared_ptr管理的对象,避免循环引用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include <iostream>
#include <memory>

class Node {
public:
int value;
std::shared_ptr<Node> next;
std::weak_ptr<Node> prev; // 使用weak_ptr避免循环引用

Node(int v) : value(v) {
std::cout << "Node constructor called for " << value << std::endl;
}

~Node() {
std::cout << "Node destructor called for " << value << std::endl;
}
};

int main() {
auto n1 = std::make_shared<Node>(1);
auto n2 = std::make_shared<Node>(2);

n1->next = n2;
n2->prev = n1;

// 使用weak_ptr
std::weak_ptr<Node> wp = n1;

// 检查对象是否仍然存在
if (auto sp = wp.lock()) {
std::cout << "Node value: " << sp->value << std::endl;
} else {
std::cout << "Node no longer exists" << std::endl;
}

// 检查引用计数
std::cout << "n1 use_count: " << n1.use_count() << std::endl;
std::cout << "n2 use_count: " << n2.use_count() << std::endl;

return 0;
}

智能指针的原理

  1. RAII机制:智能指针在构造时获取资源,在析构时释放资源
  2. 所有权管理
    • unique_ptr:独占所有权,不允许复制,只允许移动
    • shared_ptr:共享所有权,通过引用计数追踪所有者数量
    • weak_ptr:不拥有所有权,只观察shared_ptr管理的对象
  3. 析构函数:智能指针的析构函数自动调用delete或delete[]释放内存

智能指针的使用场景

  1. std::unique_ptr

    • 独占资源的场景
    • 作为函数返回值
    • 作为类成员变量
    • 管理局部动态对象
  2. std::shared_ptr

    • 多个所有者共享资源的场景
    • 跨作用域共享对象
    • 复杂数据结构(如链表、树)中的节点
    • 长时间存在的对象
  3. std::weak_ptr

    • 打破shared_ptr的循环引用
    • 观察对象而不延长其生命周期
    • 缓存场景

智能指针的最佳实践

  1. 优先使用make_unique和make_shared

    • 更安全:避免内存泄漏(如果在构造过程中抛出异常)
    • 更高效:make_shared只分配一次内存(对象和控制块)
  2. 选择合适的智能指针

    • 默认使用unique_ptr,当需要共享所有权时使用shared_ptr
    • 避免不必要的shared_ptr,因为引用计数有开销
  3. 避免裸指针与智能指针混用

    • 不要用智能指针管理已经由裸指针管理的内存
    • 不要从智能指针获取裸指针后手动释放
  4. 注意循环引用

    • 在双向引用的场景中,使用weak_ptr打破循环
  5. 正确处理数组

    • unique_ptr支持数组版本:std::unique_ptr<T[]>
    • shared_ptr需要自定义删除器来管理数组
  6. 传递智能指针

    • 对于unique_ptr,使用移动语义传递
    • 对于shared_ptr,根据需要传递值或引用

智能指针的性能考量

  1. 内存开销

    • unique_ptr:几乎无额外开销,与裸指针大小相同
    • shared_ptr:有额外开销(控制块、引用计数)
    • weak_ptr:与shared_ptr类似,需要访问控制块
  2. 时间开销

    • unique_ptr:操作几乎无开销
    • shared_ptr:引用计数的增减需要原子操作,有一定开销
    • make_shared:比直接使用new更高效
  3. 优化策略

    • 优先使用unique_ptr
    • 合理使用make_shared减少内存分配
    • 避免频繁的shared_ptr复制
    • 注意移动语义的使用

数组和指针的安全性

常见错误

  1. 空指针解引用:尝试访问空指针指向的内存
  2. 野指针:指针指向已经释放的内存
  3. 内存泄漏:动态分配的内存没有释放
  4. 数组越界:访问超出数组范围的元素
  5. 悬挂引用:引用指向已经销毁的对象
  6. 未初始化指针:使用未初始化的指针
  7. 指针类型错误:使用错误类型的指针访问内存
  8. 内存对齐问题:访问未对齐的内存导致性能下降或崩溃

安全实践

  1. 初始化指针:总是将指针初始化为nullptr或有效的内存地址
  2. 检查空指针:在使用指针前检查是否为nullptr
  3. 使用智能指针:优先使用unique_ptr和shared_ptr管理动态内存
  4. 避免裸指针:尽量减少使用裸指针,尤其是在管理动态内存时
  5. 使用std::array:对于固定大小的数组,使用std::array替代内置数组
  6. 使用std::vector:对于可变大小的数组,使用std::vector替代内置数组
  7. 边界检查:在访问数组元素时确保不越界
  8. 使用RAII:使用资源获取即初始化的原则管理资源
  9. 类型安全:使用正确的指针类型,避免类型转换错误
  10. 内存对齐:确保内存访问符合对齐要求

内存安全深入

1. 空指针和野指针的检测

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 空指针和野指针的检测

// 使用nullptr初始化
int* ptr = nullptr;

// 检查空指针
if (ptr) {
// 安全使用指针
*ptr = 42;
} else {
// 处理空指针情况
std::cout << "Pointer is null" << std::endl;
}

// 使用智能指针避免野指针
std::unique_ptr<int> safePtr = std::make_unique<int>(42);
// safePtr不会成为野指针,因为它会自动释放内存

2. 数组边界检查

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// 数组边界检查

// 使用std::array进行边界检查
#include <array>

std::array<int, 5> arr = {1, 2, 3, 4, 5};

// 安全访问(带边界检查)
for (size_t i = 0; i < arr.size(); i++) {
std::cout << arr[i] << " ";
}
std::cout << std::endl;

// 使用std::vector
#include <vector>

std::vector<int> vec = {1, 2, 3, 4, 5};

// 安全访问
for (size_t i = 0; i < vec.size(); i++) {
std::cout << vec[i] << " ";
}
std::cout << std::endl;

// 使用at()方法进行边界检查
for (size_t i = 0; i < vec.size(); i++) {
try {
std::cout << vec.at(i) << " "; // 越界会抛出异常
} catch (const std::out_of_range& e) {
std::cout << "Out of range: " << e.what() << std::endl;
}
}
std::cout << std::endl;

3. 内存泄漏检测

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
// 内存泄漏检测

// 使用智能指针避免内存泄漏
void functionWithNoLeak() {
// 智能指针自动释放内存
std::unique_ptr<int> ptr = std::make_unique<int>(42);
// 函数返回时,ptr自动销毁,释放内存
}

// 使用RAII包装资源
class FileHandle {
private:
FILE* file;
public:
explicit FileHandle(const char* filename) {
file = fopen(filename, "r");
}

~FileHandle() {
if (file) {
fclose(file);
}
}

// 禁止复制
FileHandle(const FileHandle&) = delete;
FileHandle& operator=(const FileHandle&) = delete;

// 允许移动
FileHandle(FileHandle&& other) noexcept : file(other.file) {
other.file = nullptr;
}
FileHandle& operator=(FileHandle&& other) noexcept {
if (this != &other) {
if (file) {
fclose(file);
}
file = other.file;
other.file = nullptr;
}
return *this;
}

FILE* get() const { return file; }
};

// 使用FileHandle避免资源泄漏
void readFile(const char* filename) {
FileHandle handle(filename);
// 使用handle.get()读取文件
// 函数返回时,handle自动关闭文件
}

4. 内存安全工具

C++提供了多种工具来帮助检测和防止内存安全问题:

  1. 地址 sanitizer (ASAN):检测内存错误,如缓冲区溢出、使用已释放的内存等
  2. 内存 sanitizer (MSAN):检测使用未初始化内存的情况
  3. 未定义行为 sanitizer (UBSAN):检测未定义行为,如越界访问、类型转换错误等
  4. 泄漏 sanitizer (LSAN):检测内存泄漏
  5. Valgrind:内存调试和内存泄漏检测工具
1
2
3
4
5
6
7
8
9
10
11
# 使用ASAN编译
g++ -fsanitize=address -g program.cpp -o program

# 使用MSAN编译
g++ -fsanitize=memory -g program.cpp -o program

# 使用UBSAN编译
g++ -fsanitize=undefined -g program.cpp -o program

# 使用Valgrind运行
valgrind --leak-check=full ./program

安全编码最佳实践

  1. 使用现代C++特性

    • 智能指针(unique_ptr, shared_ptr)
    • 标准容器(vector, array, map)
    • RAII原则
    • 移动语义
  2. 避免常见陷阱

    • 不使用裸指针管理动态内存
    • 不手动释放由智能指针管理的内存
    • 不使用已经释放的指针
    • 不访问超出数组范围的元素
    • 不使用未初始化的变量
  3. 防御性编程

    • 总是检查函数参数的有效性
    • 对所有输入进行验证
    • 使用异常处理错误情况
    • 记录错误信息
  4. 代码审查

    • 定期审查代码中的内存操作
    • 使用静态分析工具检测潜在问题
    • 编写单元测试验证内存安全性
  5. 性能与安全的平衡

    • 在性能关键路径上,可以使用裸指针,但要确保安全
    • 使用内存池减少内存分配开销
    • 合理使用内联汇编提高性能,但要注意安全
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
// 性能与安全的平衡示例

// 性能关键路径上的安全实践
void processLargeArray(const std::vector<int>& data) {
// 预计算大小
const size_t size = data.size();

// 使用迭代器避免边界检查开销
for (auto it = data.begin(); it != data.end(); ++it) {
// 处理元素
processElement(*it);
}
}

// 内存池示例
class MemoryPool {
private:
std::vector<char> buffer;
size_t next; // 下一个可用位置
public:
explicit MemoryPool(size_t size) : buffer(size), next(0) {}

void* allocate(size_t bytes) {
if (next + bytes > buffer.size()) {
return nullptr;
}
void* ptr = &buffer[next];
next += bytes;
return ptr;
}

void reset() {
next = 0;
}
};

// 使用内存池提高性能
void processWithPool() {
MemoryPool pool(1024 * 1024); // 1MB内存池

// 分配内存
int* arr = static_cast<int*>(pool.allocate(1000 * sizeof(int)));
if (arr) {
// 使用内存
for (int i = 0; i < 1000; i++) {
arr[i] = i;
}

// 处理数据
// ...

// 不需要手动释放内存
pool.reset(); // 重置内存池
}
}

数组和指针的应用

字符串处理

字符串是数组和指针的重要应用场景,C++提供了多种字符串处理方式:

1. C风格字符串的底层实现

C风格字符串是由字符组成的数组,以空字符('\0')结尾:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
// C风格字符串的底层实现
#include <iostream>
#include <cstring>

int main() {
// C风格字符串(字符数组)
char str1[] = "Hello"; // 等同于 char str1[] = {'H', 'e', 'l', 'l', 'o', '\0'};
char str2[20];

// 字符串复制
std::strcpy(str2, str1); // 复制到目标数组
std::cout << "str2: " << str2 << std::endl;

// 字符串连接
std::strcat(str2, " World"); // 连接到目标数组
std::cout << "str2 after concatenation: " << str2 << std::endl;

// 字符串长度
std::cout << "Length of str2: " << std::strlen(str2) << std::endl; // 不包括空字符

// 字符串比较
char str3[] = "Hello";
char str4[] = "World";

int result = std::strcmp(str3, str4);
if (result < 0) {
std::cout << str3 << " is less than " << str4 << std::endl;
} else if (result > 0) {
std::cout << str3 << " is greater than " << str4 << std::endl;
} else {
std::cout << str3 << " is equal to " << str4 << std::endl;
}

// 字符串搜索
char str5[] = "Hello, World!";
char* found = std::strchr(str5, 'W'); // 查找字符
if (found) {
std::cout << "Found 'W' at position: " << found - str5 << std::endl;
}

found = std::strstr(str5, "World"); // 查找子字符串
if (found) {
std::cout << "Found 'World' at position: " << found - str5 << std::endl;
}

return 0;
}

2. 字符串处理的性能优化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
// 字符串处理的性能优化

// 1. 避免重复计算字符串长度
void processString(const char* str) {
size_t len = std::strlen(str); // 只计算一次长度
for (size_t i = 0; i < len; i++) {
// 处理字符
}
}

// 2. 预分配足够空间
void concatenateStrings(char* dest, size_t destSize, const char* src1, const char* src2) {
size_t src1Len = std::strlen(src1);
size_t src2Len = std::strlen(src2);

if (src1Len + src2Len < destSize - 1) {
std::strcpy(dest, src1);
std::strcat(dest, src2);
} else {
// 处理缓冲区不足的情况
}
}

// 3. 使用memcpy替代strcpy(已知长度时)
void fastStringCopy(char* dest, const char* src, size_t len) {
std::memcpy(dest, src, len);
dest[len] = '\0'; // 手动添加终止符
}

// 4. 字符串视图(C++17+)
#include <string_view>

void processStringView(std::string_view sv) {
// 不需要复制字符串
std::cout << "Length: " << sv.length() << std::endl;
std::cout << "Substring: " << sv.substr(0, 5) << std::endl;
}

int main() {
const char* cstr = "Hello, World!";
std::string str = "Hello, C++!";

// 从C风格字符串创建
processStringView(cstr);

// 从std::string创建
processStringView(str);

// 直接使用字符串字面量
processStringView("Hello, string_view!");

return 0;
}

3. 字符串处理的安全实践

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
// 字符串处理的安全实践

// 1. 使用安全的字符串函数
void safeStringCopy(char* dest, size_t destSize, const char* src) {
if (dest && src && destSize > 0) {
std::strncpy(dest, src, destSize - 1);
dest[destSize - 1] = '\0'; // 确保终止符
}
}

// 2. 边界检查
void safeStringConcat(char* dest, size_t destSize, const char* src) {
if (dest && src && destSize > 0) {
size_t destLen = std::strlen(dest);
if (destLen < destSize - 1) {
size_t srcLen = std::strlen(src);
size_t copyLen = std::min(srcLen, destSize - destLen - 1);
std::strncpy(dest + destLen, src, copyLen);
dest[destLen + copyLen] = '\0';
}
}
}

// 3. 使用std::string避免缓冲区溢出
#include <string>

std::string safeConcatenate(const std::string& s1, const std::string& s2) {
return s1 + s2; // 自动管理内存
}

int main() {
// 使用安全的字符串函数
char buffer[20];
safeStringCopy(buffer, sizeof(buffer), "Hello");
std::cout << "Buffer: " << buffer << std::endl;

safeStringConcat(buffer, sizeof(buffer), " World");
std::cout << "Buffer after concat: " << buffer << std::endl;

// 使用std::string
std::string s1 = "Hello";
std::string s2 = " World";
std::string s3 = safeConcatenate(s1, s2);
std::cout << "s3: " << s3 << std::endl;

return 0;
}

函数参数

数组和指针作为函数参数是C++中常见的用法,掌握其高级技巧对于编写高效、安全的代码至关重要。

1. 数组作为函数参数的底层机制

当数组作为函数参数传递时,会发生数组到指针的衰减,即数组名会被转换为指向第一个元素的指针:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
// 数组作为函数参数的底层机制
#include <iostream>

// 数组作为函数参数(退化为指针)
void printArray(int arr[], int size) {
// arr实际上是int*类型,不是数组类型
std::cout << "Size of arr parameter: " << sizeof(arr) << std::endl; // 输出指针大小

for (int i = 0; i < size; i++) {
std::cout << arr[i] << " ";
}
std::cout << std::endl;
}

// 使用指针接收数组
void modifyArray(int* arr, int size) {
for (int i = 0; i < size; i++) {
arr[i] *= 2;
}
}

// 使用引用接收数组(保持数组类型信息)
template <size_t N>
void printArrayWithReference(int (&arr)[N]) {
// 保持数组类型信息
std::cout << "Size of arr reference: " << sizeof(arr) << std::endl; // 输出数组大小

for (int i = 0; i < N; i++) {
std::cout << arr[i] << " ";
}
std::cout << std::endl;
}

int main() {
int numbers[] = {1, 2, 3, 4, 5};
int size = sizeof(numbers) / sizeof(numbers[0]);

std::cout << "Size of numbers array: " << sizeof(numbers) << std::endl;

std::cout << "Original array: ";
printArray(numbers, size);

modifyArray(numbers, size);
std::cout << "Modified array: ";
printArray(numbers, size);

std::cout << "Using reference: ";
printArrayWithReference(numbers);

return 0;
}

2. 高级参数传递技巧

2.1 传递数组的多种方式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
// 传递数组的多种方式
#include <iostream>
#include <array>
#include <vector>

// 1. 传统方式:指针 + 大小
void processArrayTraditional(int* arr, size_t size) {
for (size_t i = 0; i < size; i++) {
arr[i] *= 2;
}
}

// 2. 数组引用(保持类型信息)
template <typename T, size_t N>
void processArrayReference(T (&arr)[N]) {
for (size_t i = 0; i < N; i++) {
arr[i] *= 2;
}
}

// 3. std::array(固定大小)
template <typename T, size_t N>
void processStdArray(std::array<T, N>& arr) {
for (auto& element : arr) {
element *= 2;
}
}

// 4. std::vector(动态大小)
template <typename T>
void processVector(std::vector<T>& vec) {
for (auto& element : vec) {
element *= 2;
}
}

// 5. 指针范围(begin + end)
template <typename T>
void processPointerRange(T* begin, T* end) {
for (T* ptr = begin; ptr != end; ++ptr) {
*ptr *= 2;
}
}

// 6. 模板参数推导

template <typename Iterator>
void processRange(Iterator begin, Iterator end) {
for (auto it = begin; it != end; ++it) {
*it *= 2;
}
}

int main() {
// 内置数组
int arr[] = {1, 2, 3, 4, 5};
size_t size = sizeof(arr) / sizeof(arr[0]);

// std::array
std::array<int, 5> stdArr = {1, 2, 3, 4, 5};

// std::vector
std::vector<int> vec = {1, 2, 3, 4, 5};

// 测试各种方式
processArrayTraditional(arr, size);
processArrayReference(arr);
processStdArray(stdArr);
processVector(vec);
processPointerRange(arr, arr + size);
processRange(stdArr.begin(), stdArr.end());

return 0;
}
2.2 指针参数的性能优化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
// 指针参数的性能优化

// 1. 按值传递小对象
void processSmallObject(int value) {
// 对于小对象(如int、char等),按值传递更高效
// 避免了指针解引用的开销
std::cout << "Value: " << value << std::endl;
}

// 2. 按const引用传递大对象
void processLargeObject(const std::string& str) {
// 对于大对象(如std::string、std::vector等),按const引用传递
// 避免了对象复制的开销
std::cout << "String length: " << str.length() << std::endl;
}

// 3. 按指针传递可选对象
void processOptionalObject(const int* ptr) {
// 对于可选参数,使用指针(可以为nullptr)
if (ptr) {
std::cout << "Value: " << *ptr << std::endl;
} else {
std::cout << "No value provided" << std::endl;
}
}

// 4. 按引用传递输出参数
void calculateValues(int input, int& output1, int& output2) {
// 对于输出参数,使用引用
// 避免了指针的空检查
output1 = input * 2;
output2 = input * 3;
}

// 5. 移动语义(C++11+)
void processWithMove(std::string&& str) {
// 对于临时对象,使用移动语义
// 避免了对象复制的开销
std::cout << "String: " << str << std::endl;
}

int main() {
int x = 42;
std::string str = "Hello, C++!";

// 测试各种传递方式
processSmallObject(x);
processLargeObject(str);
processOptionalObject(&x);
processOptionalObject(nullptr);

int a, b;
calculateValues(x, a, b);
std::cout << "a: " << a << ", b: " << b << std::endl;

processWithMove(std::move(str));

return 0;
}
2.3 类型安全的参数传递
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
// 类型安全的参数传递

// 1. 使用枚举替代魔法数字
enum class Color {
Red,
Green,
Blue
};

void setColor(Color color) {
// 类型安全的枚举参数
switch (color) {
case Color::Red:
std::cout << "Red" << std::endl;
break;
case Color::Green:
std::cout << "Green" << std::endl;
break;
case Color::Blue:
std::cout << "Blue" << std::endl;
break;
}
}

// 2. 使用强类型别名(C++11+)
using Meter = double;
using Kilometer = double;

// 类型安全的距离转换
Kilometer metersToKilometers(Meter meters) {
return meters / 1000.0;
}

// 3. 使用结构体封装相关参数
struct PersonInfo {
std::string name;
int age;
std::string address;
};

void processPerson(const PersonInfo& info) {
// 结构化参数,提高可读性和类型安全性
std::cout << "Name: " << info.name << std::endl;
std::cout << "Age: " << info.age << std::endl;
std::cout << "Address: " << info.address << std::endl;
}

// 4. 使用模板约束(C++20+)
#include <concepts>

template <typename T>
concept Numeric = std::integral<T> || std::floating_point<T>;

template <Numeric T>
T sum(T a, T b) {
// 只接受数值类型
return a + b;
}

int main() {
// 测试类型安全的参数
setColor(Color::Red);

Meter distance = 5000.0;
Kilometer km = metersToKilometers(distance);
std::cout << "Distance: " << km << " km" << std::endl;

PersonInfo info = {"Alice", 30, "123 Main St"};
processPerson(info);

std::cout << "Sum: " << sum(1, 2) << std::endl;
std::cout << "Sum: " << sum(1.5, 2.5) << std::endl;

return 0;
}

多维数组和指针

多维数组是数组的扩展,它在内存中以连续的方式存储,掌握其内存布局和指针操作对于编写高效的代码至关重要。

1. 多维数组的内存布局

多维数组在内存中是按行优先的顺序连续存储的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// 多维数组的内存布局
#include <iostream>

int main() {
// 二维数组
int matrix[2][3] = {{1, 2, 3}, {4, 5, 6}};

// 内存布局:1, 2, 3, 4, 5, 6(连续存储)
std::cout << "Memory layout of 2D array:" << std::endl;
for (int i = 0; i < 2; i++) {
for (int j = 0; j < 3; j++) {
std::cout << "matrix[" << i << "][" << j << "] = " << matrix[i][j];
std::cout << " (address: " << &matrix[i][j] << ")" << std::endl;
}
}

// 三维数组
int cube[2][2][2] = {{{1, 2}, {3, 4}}, {{5, 6}, {7, 8}}};

// 内存布局:1, 2, 3, 4, 5, 6, 7, 8(连续存储)
std::cout << "\nMemory layout of 3D array:" << std::endl;
for (int i = 0; i < 2; i++) {
for (int j = 0; j < 2; j++) {
for (int k = 0; k < 2; k++) {
std::cout << "cube[" << i << "][" << j << "][" << k << "] = " << cube[i][j][k];
std::cout << " (address: " << &cube[i][j][k] << ")" << std::endl;
}
}
}

return 0;
}

2. 多维数组的指针操作

2.1 指向多维数组的指针类型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// 指向多维数组的指针类型
#include <iostream>

int main() {
int matrix[2][3] = {{1, 2, 3}, {4, 5, 6}};

// 1. 指向元素的指针(int*)
int* elemPtr = &matrix[0][0];
std::cout << "Using element pointer:" << std::endl;
for (int i = 0; i < 2 * 3; i++) {
std::cout << elemPtr[i] << " ";
}
std::cout << std::endl;

// 2. 指向行的指针(int (*)[3])
int (*rowPtr)[3] = matrix;
std::cout << "Using row pointer:" << std::endl;
for (int i = 0; i < 2; i++) {
for (int j = 0; j < 3; j++) {
std::cout << rowPtr[i][j] << " ";
}
std::cout << std::endl;
}

// 3. 指向整个数组的指针(int (*)[2][3])
int (*arrPtr)[2][3] = &matrix;
std::cout << "Using array pointer:" << std::endl;
for (int i = 0; i < 2; i++) {
for (int j = 0; j < 3; j++) {
std::cout << (*arrPtr)[i][j] << " ";
}
std::cout << std::endl;
}

return 0;
}
2.2 多维数组作为函数参数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
// 多维数组作为函数参数
#include <iostream>

// 1. 固定列数的二维数组
void print2DArrayFixedCols(int arr[][3], int rows) {
for (int i = 0; i < rows; i++) {
for (int j = 0; j < 3; j++) {
std::cout << arr[i][j] << " ";
}
std::cout << std::endl;
}
}

// 2. 使用指向行的指针
void print2DArrayPointer(int (*arr)[3], int rows) {
for (int i = 0; i < rows; i++) {
for (int j = 0; j < 3; j++) {
std::cout << arr[i][j] << " ";
}
std::cout << std::endl;
}
}

// 3. 使用模板推导维度
template <size_t Rows, size_t Cols>
void print2DArrayTemplate(int (&arr)[Rows][Cols]) {
for (size_t i = 0; i < Rows; i++) {
for (size_t j = 0; j < Cols; j++) {
std::cout << arr[i][j] << " ";
}
std::cout << std::endl;
}
}

// 4. 使用指针的指针(动态分配的二维数组)
void printUsingDoublePointer(int** arr, int rows, int cols) {
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
std::cout << arr[i][j] << " ";
}
std::cout << std::endl;
}
}

int main() {
// 静态二维数组
int matrix[2][3] = {{1, 2, 3}, {4, 5, 6}};
std::cout << "Static 2D array (fixed cols): " << std::endl;
print2DArrayFixedCols(matrix, 2);

std::cout << "Static 2D array (pointer): " << std::endl;
print2DArrayPointer(matrix, 2);

std::cout << "Static 2D array (template): " << std::endl;
print2DArrayTemplate(matrix);

// 动态二维数组(使用指针的指针)
int rows = 2;
int cols = 3;

// 分配行指针
int** dynamicMatrix = new int*[rows];

// 分配每一行
for (int i = 0; i < rows; i++) {
dynamicMatrix[i] = new int[cols];
}

// 初始化
int value = 1;
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
dynamicMatrix[i][j] = value++;
}
}

std::cout << "Dynamic 2D array: " << std::endl;
printUsingDoublePointer(dynamicMatrix, rows, cols);

// 释放内存
for (int i = 0; i < rows; i++) {
delete[] dynamicMatrix[i];
}
delete[] dynamicMatrix;

return 0;
}
2.3 多维数组的性能优化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
// 多维数组的性能优化
#include <iostream>
#include <chrono>

// 传统访问方式
void traditionalAccess(int** matrix, int rows, int cols) {
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
matrix[i][j] = i * cols + j;
}
}
}

// 线性访问方式(利用内存连续性)
void linearAccess(int* matrix, int rows, int cols) {
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
matrix[i * cols + j] = i * cols + j;
}
}
}

// 使用智能指针管理动态多维数组
#include <memory>

std::unique_ptr<int[]> create2DArray(int rows, int cols) {
// 分配连续内存
auto matrix = std::make_unique<int[]>(rows * cols);

// 初始化
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
matrix[i * cols + j] = i * cols + j;
}
}

return matrix;
}

int main() {
const int rows = 1000;
const int cols = 1000;

// 测试传统访问方式
int** matrix1 = new int*[rows];
for (int i = 0; i < rows; i++) {
matrix1[i] = new int[cols];
}

auto start1 = std::chrono::high_resolution_clock::now();
traditionalAccess(matrix1, rows, cols);
auto end1 = std::chrono::high_resolution_clock::now();
std::chrono::duration<double> elapsed1 = end1 - start1;
std::cout << "Traditional access time: " << elapsed1.count() << " seconds" << std::endl;

// 释放内存
for (int i = 0; i < rows; i++) {
delete[] matrix1[i];
}
delete[] matrix1;

// 测试线性访问方式
int* matrix2 = new int[rows * cols];

auto start2 = std::chrono::high_resolution_clock::now();
linearAccess(matrix2, rows, cols);
auto end2 = std::chrono::high_resolution_clock::now();
std::chrono::duration<double> elapsed2 = end2 - start2;
std::cout << "Linear access time: " << elapsed2.count() << " seconds" << std::endl;

// 释放内存
delete[] matrix2;

// 测试智能指针方式
auto start3 = std::chrono::high_resolution_clock::now();
auto matrix3 = create2DArray(rows, cols);
auto end3 = std::chrono::high_resolution_clock::now();
std::chrono::duration<double> elapsed3 = end3 - start3;
std::cout << "Smart pointer time: " << elapsed3.count() << " seconds" << std::endl;

return 0;
}
2.4 多维数组的现代C++替代方案
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
// 多维数组的现代C++替代方案
#include <iostream>
#include <vector>
#include <array>

int main() {
// 1. 使用std::vector
std::vector<std::vector<int>> vec2D = {
{1, 2, 3},
{4, 5, 6},
{7, 8, 9}
};

std::cout << "Using std::vector:" << std::endl;
for (const auto& row : vec2D) {
for (int value : row) {
std::cout << value << " ";
}
std::cout << std::endl;
}

// 2. 使用std::array(固定大小)
std::array<std::array<int, 3>, 2> arr2D = {
{{1, 2, 3},
{4, 5, 6}}
};

std::cout << "Using std::array:" << std::endl;
for (const auto& row : arr2D) {
for (int value : row) {
std::cout << value << " ";
}
std::cout << std::endl;
}

// 3. 使用单个vector模拟二维数组(连续内存)
int rows = 2;
int cols = 3;
std::vector<int> flatVec(rows * cols);

// 初始化
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
flatVec[i * cols + j] = i * cols + j + 1;
}
}

std::cout << "Using flat std::vector:" << std::endl;
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
std::cout << flatVec[i * cols + j] << " ";
}
std::cout << std::endl;
}

return 0;
}

总结

数组和指针是C++中最核心、最强大的概念之一,它们构成了C++高效性能的基础。通过本章的深入学习,你应该掌握了以下关键知识点:

核心概念与原理

  1. 数组的底层实现

    • 连续内存存储与固定大小
    • 数组到指针的衰减机制
    • 多维数组的行优先存储布局
    • 数组的类型特性与引用
  2. 指针的高级特性

    • 指针算术的底层原理与边界检查
    • 指针类型与类型安全
    • 函数指针与成员指针的应用
    • 指针与引用的区别和联系
  3. 动态内存管理

    • new/delete的底层实现与重载
    • 内存对齐与性能优化
    • 自定义内存分配器的设计
    • 内存池技术与缓存友好性
  4. 智能指针的设计与实现

    • std::unique_ptr的独占所有权语义
    • std::shared_ptr的引用计数机制
    • std::weak_ptr的循环引用解决方案
    • 自定义删除器的应用场景

现代C++实践

  1. 现代C++特性

    • 移动语义与智能指针的结合
    • std::make_uniquestd::make_shared的最佳实践
    • std::arraystd::vector的性能对比
    • std::string_view与现代字符串处理
  2. 性能优化策略

    • 利用内存连续性提高访问速度
    • 减少指针解引用开销
    • 合理选择参数传递方式
    • 内存池与缓存友好的数据结构设计
  3. 内存安全实践

    • 智能指针替代裸指针的最佳实践
    • RAII原则管理资源
    • 内存安全工具(ASAN、MSAN、UBSAN)的使用
    • 防御性编程技巧与错误处理

工程应用与最佳实践

  1. 字符串处理

    • C风格字符串的底层操作与优化
    • std::string的SSO与高级特性
    • Unicode处理与国际化支持
    • 字符串处理的性能优化策略
  2. 函数参数传递

    • 数组与指针作为参数的最佳实践
    • 类型安全的参数设计
    • 性能与安全性的平衡
    • 现代C++的参数传递方式
  3. 多维数据处理

    • 多维数组的内存布局与访问模式
    • 连续内存与分散内存的性能对比
    • 现代C++的多维数据结构
    • 大型数据的分块处理与并行计算
  4. 跨平台与兼容性

    • 指针大小与内存模型的平台差异
    • 内存对齐的跨平台处理
    • 不同编译器的实现差异
    • 可移植代码的编写技巧

专业发展方向

  1. 系统编程

    • 内存管理与优化
    • 指针操作与位运算
    • 硬件接口与设备驱动开发
  2. 性能优化

    • 内存访问模式优化
    • 缓存利用率提升
    • 内存分配策略设计
  3. 安全编程

    • 内存安全漏洞防护
    • 指针操作的安全性
    • 静态分析与代码审查
  4. 现代C++设计

    • 智能指针的高级应用
    • 内存管理的抽象设计
    • 资源管理的最佳实践

学习建议

  1. 深入理解内存模型

    • 了解计算机内存的工作原理
    • 掌握C++的内存管理机制
    • 学习现代操作系统的内存管理
  2. 实践与实验

    • 编写各种数组和指针的示例代码
    • 测试不同内存访问模式的性能
    • 分析智能指针的行为与性能
  3. 阅读优秀代码

    • 学习标准库的实现
    • 研究开源项目中的内存管理
    • 分析高性能C++代码的设计
  4. 持续学习

    • 关注C++标准的发展
    • 学习现代C++的最佳实践
    • 了解行业前沿的内存管理技术

数组和指针不仅是C++的基础,也是理解计算机系统和编写高性能代码的关键。通过深入掌握这些概念,你将能够编写更加高效、安全、可维护的C++代码,为成为一名专业的C++开发者打下坚实的基础。