C++内存管理与指针操作详解
立即解锁
发布时间: 2025-08-16 01:20:52 阅读量: 2 订阅数: 31 


C++编程语言第四版:核心概念与技术
### C++ 内存管理与指针操作详解
#### 1. 字符串复制优化
在 C++ 中,字符串复制是常见操作。最初的字符串复制代码如下:
```cpp
while (*q != 0) {
*p++ = *q++;
}
*p = 0; // terminating zero
```
这里通过循环将 `q` 指向的字符串逐个字符复制到 `p` 指向的位置,最后添加终止符 `0`。
由于 `*p++ = *q++` 的值就是 `*q`,我们可以将代码改写为:
```cpp
while ((*p++ = *q++) != 0) { }
```
在这个版本中,当 `*q` 为 `0` 时,我们已经将其复制到 `*p` 并对 `p` 进行了递增操作,所以可以省略最后的终止符赋值操作。
进一步优化,我们发现空代码块和 `!= 0` 条件是多余的,因为整数条件的结果总是与 `0` 进行比较,最终得到简洁版本:
```cpp
while (*p++ = *q++) ;
```
对于有经验的 C 或 C++ 程序员来说,这个版本的可读性并不差。而且除了调用 `strlen()` 的第一个版本外,性能基本相当,编译器通常会生成相同的代码。
最有效的复制以零结尾的字符串的方法通常是使用标准 C 风格的字符串复制函数:
```cpp
char* strcpy(char*, const char*);
// from <string.h>
```
对于更通用的复制操作,可以使用标准复制算法。尽可能使用标准库功能,而不是手动操作指针和字节,因为标准库函数可能会被内联,甚至使用专门的机器指令实现。在认为手动编写的代码性能优于库函数之前,应该仔细测试,因为在不同的硬件和编译器组合下,优势可能并不存在,而且维护起来可能会很麻烦。
#### 2. 自由存储区(Free Store)
在 C++ 中,命名对象的生命周期由其作用域决定。但有时需要创建独立于创建它的作用域的对象,例如在函数返回后仍能使用的对象。`new` 运算符可以创建这样的对象,`delete` 运算符用于销毁它们。通过 `new` 分配的对象被称为“在自由存储区”(也称为“在堆上”或“在动态内存中”)。
以下是一个编译器中语法分析函数构建表达式树的示例:
```cpp
struct Enode {
Token_value oper;
Enode* left;
Enode* right;
// ...
};
Enode* expr(bool get)
{
Enode* left = term(get);
for (;;) {
switch (ts.current().kind) {
case Kind::plus:
case Kind::minus:
left = new Enode {ts.current().kind,left,term(true)};
break;
default:
return left;
// return node
}
}
}
```
在 `Kind::plus` 和 `Kind::minus` 情况下,会在自由存储区创建一个新的 `Enode` 对象,并使用 `{ts.current().kind,left,term(true)}` 进行初始化,最终将结果指针赋值给 `left` 并从 `expr()` 函数返回。
使用 `{}` 列表符号指定参数,也可以使用旧式的 `()` 列表符号指定初始化器,但使用 `=` 符号初始化 `new` 创建的对象会导致错误,例如:
```cpp
int* p = new int = 7; // error
```
如果类型有默认构造函数,可以省略初始化器,但内置类型默认是未初始化的。例如:
```cpp
auto pc = new complex<double>;
// the complex is initialized to {0,0}
auto pi = new int;
// the int is uninitialized
```
为了确保得到默认初始化,使用 `{}`:
```cpp
auto pc = new complex<double>{}; // the complex is initialized to {0,0}
auto pi = new int{};
// the int is initialized to 0
```
代码生成器可以使用 `expr()` 创建的 `Enode` 对象并进行删除:
```cpp
void generate(Enode* n)
{
switch (n->oper) {
case Kind::plus:
// use n
delete n; // delete an Enode from the free store
}
}
```
通过 `new` 创建的对象会一直存在,直到被 `delete` 显式销毁,之后其占用的空间可以被 `new` 再次使用。C++ 实现不保证存在“垃圾回收器”来处理未引用的对象,因此通常需要手动使用 `delete` 释放对象。
`delete` 运算符只能应用于 `new` 返回的指针或 `nullptr`,对 `nullptr` 使用 `delete` 没有效果。如果删除的对象是具有析构函数的类的对象,`delete` 会在释放对象内存之前调用析构函数。
#### 3. 内存管理问题
自由存储区存在以下主要问题:
- **对象泄漏**:使用 `new` 分配对象后忘记使用 `delete` 删除。
- **过早删除**:删除一个对象后,仍然使用指向该对象的其他指针。
- **双重删除**:一个对象被删除两次,可能会导致析构函数被调用两次。
对象泄漏可能会导致程序耗尽空间,过早删除通常会导致指针不再指向有效对象,读写该指针可能会得到错误结果或破坏其他对象。双重删除是一个严重问题,因为资源管理器通常无法跟踪资源的所有权,可能会导致未定义行为,结果不可预测且通常是灾难性的。
以下是一个非常糟糕的代码示例:
```cpp
int* p1 = new int{99};
int* p2 = p1;
// potential trouble
delete p1;
// now p2 doesn't point to a valid object
p1 = nullptr;
// gives a false sense of safety
char* p3 = new char{'x'};
// p3 may now point to the memory pointed to by p2
*p2 = 999;
// this may cause trouble
cout << *p3 << '\n';
// may not print x
```
```cpp
void sloppy() // very bad code
{
int* p = new int[1000];
// acquire memory
// ... use *p ...
delete[] p;
// release memory
// ... wait a while ...
delete[] p;
// but sloppy() does not own *p
}
```
为了避免这些问题,可以采用以下两种资源管理方法:
1. **避免不必要的自由存储区使用**:如果不需要,尽量使用作用域内的变量。
2. **使用资源管理对象**:当在自由存储区创建对象时,将其指针放入具有析构函数的管理对象(有时称为句柄)中。例如 `string`、`vector` 等标准库容器,以及 `unique_ptr` 和 `shared_ptr` 等智能指针。尽可能让管理对象成为作用域内的变量。许多传统的自由存储区使用场景可以通过移动语义来避免。
以下是使用 `vector` 和 `string` 的示例:
```cpp
void f(const
```
0
0
复制全文
相关推荐








