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也会被删除,但返回值是堆上对象的指针,因此无需创建临时对象。

对象的构造和析构顺序

单个对象的构造和析构顺序

析构函数与对应构造函数的调用顺序相反。

构造函数的调用顺序:

  1. 调用父类的构造函数。
  2. 调用成员变量的构造函数(调用顺序与声明顺序相同)。
  3. 调用自身的构造函数。

多个对象的构造和析构顺序

多个对象析构时,析构顺序与构造顺序相反。

栈上对象的构造顺序:

  • 当程序执行流到达对象的定义语句时进行构造。

堆上对象的构造顺序:

  • 当程序执行流到达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寄存器),因此可以在函数外部访问到。

返回值被复制到的地方可以是寄存器或者内存,这取决于编译器的优化等因素。有些编译器使用寄存器来作为返回值的存储位置,这样可以避免对内存的读写操作,提高函数的执行效率。而有些编译器则会使用内存来存储返回值,例如用栈上的内存或者堆上的内存来保存返回值。在使用栈上内存存储返回值时,会将返回值的地址传递给调用者,然后调用者负责处理返回值的存储。而在使用堆内存存储返回值时,函数会在堆上分配一块内存来存储返回值,然后将堆上内存的地址返回给调用者。

posted @ 2023-03-25 14:13  3的4次方  阅读(70)  评论(0编辑  收藏  举报