C++中在栈和堆中创建对象以及引发的思考
C++中在栈和堆中创建对象以及引发的思考
建立对象有2种方式
- 静态创建:
- 如
Obj obj
,是编译器在栈中为对象分配内存,通过移动栈顶指针挪出适当的空间,然后在这片内存空间是哪个调用构造函数创建对象 - 栈由编译器自动分配和释放,存放函数的参数值、局部变量的值、对象的引用地址等
- 如
- 动态创建:
- 如
Obj *p = new Obj()
,是用new
操作符将对象建立在堆中,而栈中只保留指向该对象的指针用于操纵该对象 - 堆由程序员负责分配和释放,存放生命周期内动态创建的对象
- 如
以下是示例:
// test.cpp
#include <iostream>
#include <cstring>
using namespace std;
class Student {
public:
Student() = default;
Student(string name, int age) :name(name), age(age) {}
Student(const Student& rhs) {
name = rhs.name;
age = rhs.age;
cout << name << " 拷贝构造函数调用!" << endl;
}
~Student() {
cout << name << " 析构函数调用!" << endl;
}
void show() {
cout << name << " " << age << endl;
}
private:
string name = "test";
int age = 1;
};
Student createStatic(string name, int age) {
Student stu(name, age);//静态创建
return stu;
}
Student* createDynamic(string name, int age) {
Student *xiaohong = new Student(name, age); //动态创建
return xiaohong;
}
void test1() { // 静态创建
cout << "静态创建" << endl;
Student xiaoming = createStatic("小明", 18);
xiaoming.show();
}
void test2() { // 动态创建
cout << "动态创建" << endl;
Student *pstu = createDynamic("小红", 19);
pstu->show();
delete pstu;
}
int main() {
test1();
test2();
return 0;
}
/*输出:
静态创建
小明 拷贝构造函数调用!
小明 析构函数调用!
小明 18
小明 析构函数调用!
动态创建
小红 19
小红 析构函数调用!
*/
编译时关闭编译器优化
g++ test.cpp -o test -fno-elide-constructors
解释:
test1()
中在栈上创建了一个对象xiaoming
,而离开函数createStatic()
后,在栈上的局部对象stu
会被释放,但是由于stu
是作为函数的返回值,因此在返回时会调用拷贝构造函数,创建临时对象存储返回值,从而给xiaoming
。
而test2()
中是在堆上创建的对象,在栈上仅有指针。在离开函数createDynamic()
后,指针xiaohong
也会被删除,但返回值是堆上对象的指针,因此无需创建临时对象。
对象的构造和析构顺序
单个对象的构造和析构顺序
析构函数与对应构造函数的调用顺序相反。
构造函数的调用顺序:
- 调用父类的构造函数。
- 调用成员变量的构造函数(调用顺序与声明顺序相同)。
- 调用自身的构造函数。
多个对象的构造和析构顺序
多个对象析构时,析构顺序与构造顺序相反。
栈上对象的构造顺序:
- 当程序执行流到达对象的定义语句时进行构造。
堆上对象的构造顺序:
- 当程序执行流到达
new
语句时创建对象。 - 使用
new
创建对象将自动触发构造函数的调用。
析构函数的调用顺序:
- 对于栈对象和全局对象,类似于入栈与出栈的顺序(因为栈帧FIFO),因此按照创建的逆序析构
- 堆对象的析构发生在使用
delete
的时候,与delete
的使用顺序相关。
关于函数返回值的思考
如以下程序,函数返回值按理来说是函数体内部的局部变量,在函数返回后,该局部变量的声明周期就结束了,栈上就不存在该局部变量了,那么是如何做到函数能够返回值的呢?
#include <iostream>
int func() {
int a = 2;
return a;
}
int main() {
int b = func(); // b是如何获取到a的值的
std::cout << b;
return 0;
}
事实上,在函数调用时,编译器会在堆栈上为函数分配一块空间,其中包括函数的调用参数,函数体内部的局部变量以及返回值。当函数执行完毕后,返回值被复制到一个特定的寄存器或者内存单元中,然后函数的堆栈帧被弹出,函数的局部变量和参数的内存被释放。所以虽然局部变量的生命周期已经结束了,但是返回值已经被保存在某个地方(我理解为计组微指令那里提到的RA寄存器),因此可以在函数外部访问到。
返回值被复制到的地方可以是寄存器或者内存,这取决于编译器的优化等因素。有些编译器使用寄存器来作为返回值的存储位置,这样可以避免对内存的读写操作,提高函数的执行效率。而有些编译器则会使用内存来存储返回值,例如用栈上的内存或者堆上的内存来保存返回值。在使用栈上内存存储返回值时,会将返回值的地址传递给调用者,然后调用者负责处理返回值的存储。而在使用堆内存存储返回值时,函数会在堆上分配一块内存来存储返回值,然后将堆上内存的地址返回给调用者。