1. 内存分区
在 C++ 里,内存主要分为以下几个区域:
- 栈(Stack):由编译器自动分配和释放,用于存储局部变量、函数参数和返回地址等。其特点是内存分配和释放速度快,遵循后进先出(LIFO)原则。例如:
#include <iostream>void func() {int a = 10; // 变量 a 存储在栈上std::cout << a << std::endl;
}int main() {func();return 0;
}
- 堆(Heap):由程序员手动管理,通过
new
和delete
(或malloc
和free
)进行内存的分配和释放。堆的空间较大,但分配和释放的速度相对较慢,且容易出现内存泄漏问题。例如:
#include <iostream>int main() {int* ptr = new int(10); // 在堆上分配内存std::cout << *ptr << std::endl;delete ptr; // 释放堆上的内存return 0;
}
- 全局 / 静态区(Global/Static Area):用于存储全局变量和静态变量。在程序启动时分配内存,程序结束时释放。例如:
#include <iostream>int globalVar = 20; // 全局变量,存储在全局/静态区void func() {static int staticVar = 30; // 静态变量,存储在全局/静态区std::cout << staticVar << std::endl;
}int main() {func();return 0;
}
- 常量区(Constant Area):用于存储常量数据,如字符串常量。这些数据在程序运行期间不可修改。例如:
#include <iostream>int main() {const char* str = "Hello"; // 字符串常量存储在常量区std::cout << str << std::endl;return 0;
}
- 代码区(Code Area):用于存储程序的可执行代码。
2. new
和 delete
与 malloc
和 free
的区别
- 类型安全性:
new
和delete
是 C++ 的运算符,具有类型安全性,会自动计算所需内存的大小,并返回正确类型的指针。而malloc
和free
是 C 语言的函数,返回void*
类型的指针,需要手动进行类型转换。例如:
#include <iostream>int main() {int* ptr1 = new int(10); // new 自动处理类型int* ptr2 = (int*)malloc(sizeof(int)); // malloc 需要手动类型转换delete ptr1;free(ptr2);return 0;
}
- 构造和析构:
new
在分配内存后会调用对象的构造函数进行初始化,delete
在释放内存前会调用对象的析构函数进行资源清理。而malloc
和free
只是单纯地分配和释放内存,不会调用构造和析构函数。例如:
#include <iostream>class MyClass {
public:MyClass() { std::cout << "Constructor" << std::endl; }~MyClass() { std::cout << "Destructor" << std::endl; }
};int main() {MyClass* obj1 = new MyClass(); // 调用构造函数delete obj1; // 调用析构函数MyClass* obj2 = (MyClass*)malloc(sizeof(MyClass)); // 不调用构造函数free(obj2); // 不调用析构函数return 0;
}
- 异常处理:
new
在内存分配失败时会抛出std::bad_alloc
异常,而malloc
在内存分配失败时返回NULL
指针。
3. 内存泄漏
内存泄漏指的是程序在动态分配内存后,由于某种原因未能正确释放这些内存,导致这部分内存无法被再次使用。常见的原因包括忘记调用 delete
或 free
,或者在异常处理中没有正确释放内存。例如:
#include <iostream>void memoryLeak() {int* ptr = new int(10);// 忘记释放内存// delete ptr;
}int main() {memoryLeak();return 0;
}
为避免内存泄漏,可以使用智能指针(如 std::unique_ptr
、std::shared_ptr
和 std::weak_ptr
),它们会自动管理内存的生命周期。例如:
#include <iostream>
#include <memory>void noMemoryLeak() {std::unique_ptr<int> ptr = std::make_unique<int>(10);// 不需要手动释放内存,ptr 离开作用域时会自动释放
}int main() {noMemoryLeak();return 0;
}
4. 悬空指针
悬空指针是指指向已经被释放的内存的指针。使用悬空指针会导致未定义行为。例如:
#include <iostream>int main() {int* ptr = new int(10);delete ptr;// ptr 现在是悬空指针// *ptr = 20; // 未定义行为return 0;
}
为避免悬空指针问题,在释放内存后将指针置为 nullptr
。例如:
#include <iostream>int main() {int* ptr = new int(10);delete ptr;ptr = nullptr; // 将指针置为 nullptrreturn 0;
}
5. 浅拷贝和深拷贝
- 浅拷贝:浅拷贝只是简单地复制对象的成员变量的值,对于指针成员,只是复制指针的值,而不复制指针所指向的内存。这会导致多个对象共享同一块内存,当其中一个对象释放内存时,其他对象的指针就会成为悬空指针。例如:
收起
cpp
#include <iostream>class MyClass {
public:int* data;MyClass(int value) {data = new int(value);}// 浅拷贝构造函数MyClass(const MyClass& other) {data = other.data;}~MyClass() {delete data;}
};int main() {MyClass obj1(10);MyClass obj2(obj1); // 浅拷贝// 这里会出现问题,因为 obj1 和 obj2 共享同一块内存return 0;
}
- 深拷贝:深拷贝会为新对象的指针成员分配新的内存,并将原对象指针所指向的内存内容复制到新的内存中。这样每个对象都有自己独立的内存副本,避免了悬空指针问题。例如:
收起
cpp
#include <iostream>class MyClass {
public:int* data;MyClass(int value) {data = new int(value);}// 深拷贝构造函数MyClass(const MyClass& other) {data = new int(*other.data);}~MyClass() {delete data;}
};int main() {MyClass obj1(10);MyClass obj2(obj1); // 深拷贝return 0;
}
6. 智能指针
智能指针是 C++ 标准库提供的模板类,用于自动管理动态分配的内存,避免手动管理内存带来的问题。常见的智能指针有:
std::unique_ptr
:独占所有权的智能指针,同一时间只能有一个std::unique_ptr
指向某个对象。例如:
收起
cpp
#include <iostream>
#include <memory>int main() {std::unique_ptr<int> ptr = std::make_unique<int>(10);// std::unique_ptr<int> ptr2 = ptr; // 错误,不能复制std::unique_ptr<int> ptr2 = std::move(ptr); // 可以转移所有权return 0;
}
std::shared_ptr
:共享所有权的智能指针,多个std::shared_ptr
可以指向同一个对象,使用引用计数来管理对象的生命周期,当引用计数为 0 时,对象会被自动释放。例如:
收起
cpp
#include <iostream>
#include <memory>int main() {std::shared_ptr<int> ptr1 = std::make_shared<int>(10);std::shared_ptr<int> ptr2 = ptr1; // 可以复制return 0;
}
std::weak_ptr
:弱引用的智能指针,它不拥有对象的所有权,主要用于解决std::shared_ptr
的循环引用问题。例如:
#include <iostream>
#include <memory>class B;class A {
public:std::shared_ptr<B> bPtr;~A() { std::cout << "A destructor" << std::endl; }
};class B {
public:std::weak_ptr<A> aPtr; // 使用 std::weak_ptr 避免循环引用~B() { std::cout << "B destructor" << std::endl; }
};int main() {std::shared_ptr<A> a = std::make_shared<A>();std::shared_ptr<B> b = std::make_shared<B>();a->bPtr = b;b->aPtr = a;return 0;
}