我正在学习c ++,并想知道这样的程序是如何在主内存中组织的。我知道有一个堆栈(带有堆栈框架)和一个堆。我知道动态分配的东西会在堆上分配它。这是由malloc
或new
等运营商完成的。但我不能在这个小型的c ++程序中看到它们。
该程序由一个主类和一个名为MyClass
的类组成。这堂课有:
int
)main-class定义Myclass的对象,并定义指向该对象的指针。
那么 - 这一切如何在记忆中组织起来?
#include <iostream>
using namespace std;
class MyClass {
int i;
public:
MyClass(int n) {
i = n;
}
int get_nmbr() {
return this->i;
}
};
int main() {
MyClass myClass(100), *p;
cout << myClass.get_nmbr() << endl;
p = &myClass;
cout << p;
return 0;
}
答案 0 :(得分:6)
让我们逐行了解。
int main() {
一个新函数从这一行开始,然后是一个新的scope。
MyClass myClass(100), *p;
这里发生了两件事。一,变量myClass
在函数的作用域内声明,这使它成为一个局部变量,因此它被分配在堆栈中。编译器将发出在堆栈上保留足够空间的机器指令(通常通过碰撞sp
堆栈指针寄存器),然后执行对类构造函数的调用。传递给构造函数的this
指针是堆栈分配的基础。
第二个变量p
只是一个本地指针,编译器(取决于优化)可以将此值存储在本地堆栈或寄存器中。
cout << myClass.get_nmbr() << endl;
调用本地get_nmbr()
实例的myClass
方法。同样,this
指针指向本地堆栈帧分配。此函数查找实例变量i
的值并将其返回给调用者。请注意,因为对象是在堆栈帧上分配的,所以i
也存在于堆栈帧上。
p = &myClass;
将myClass
实例的地址存储在变量p
中。这是一个堆栈地址。
cout << p;
return 0;
}
打印出局部变量p
并返回。
您的所有代码仅与堆栈分配有关。结果是当函数的作用域在执行时被保持/关闭(例如函数返回)时,对象将被自动“解构”并释放其内存。如果你从该函数返回了类似p
的指针,那么你正在查看dangling pointer,即指向一个被释放和破坏的对象的指针。 (根据语言标准,通过这种悬空指针进行内存访问的行为是“未定义的”。)
如果要在堆上分配对象,并因此将其生命周期扩展到声明它的范围之外,那么可以在C ++中使用new operator。在幕后,new
拨打malloc,然后拨打适当的constructor。
您可以将上面的示例扩展为以下内容:
{
MyClass stackObj(100); // Allocate an instance of MyClass on the function's stack frame
MyClass *heapObj = new MyClass(100); // Allocate an instance of MyClass from the process heap.
printf("stack = %p heap = %p\n", stackObj, heapObj);
// Scope closes, thus call the stackObj destructor, but no need to free stackObj memory as this is done automatically when the containing function returns.
delete heapObj; // Call heapObj destructor and free the heap allocation.
}
注意:在此上下文中,您可能需要查看placement new,以及auto pointers和shared pointers。
答案 1 :(得分:2)
首先要做的事情。在C ++中,您不应该使用malloc
。
在此程序中,所有使用的内存都在堆栈中。让我们一次看一个:
MyClass myClass(100);
myClass
是堆栈上的自动变量,大小等于sizeof(MyClass);
。这包括成员i
。
MyClass *p;
p
是堆栈上的自动变量,指向MyClass
的实例。由于它没有初始化,它可以指向任何地方,不应该使用。它的大小等于sizeof(MyClass*);
,它可能(但不一定)在myClass
之后立即放在堆栈上。
cout << myClass.get_nmbr() << endl;
MyClass::get_nmbr()
返回i
实例的成员myClass
的副本。这个副本可能已经过优化,因此不会占用任何额外的内存。如果确实如此,它将在堆栈中。 ostream& operator<< (int val);
的行为是特定于实现的。
p = &myClass;
此处p
指向有效的MyClass
实例,但由于实例已存在,因此内存布局没有任何变化。
cout << p;
输出myClass
的地址。 operator<<
的行为再一次是特定于实现的。
This program(有点)可视化自动变量的布局。
答案 2 :(得分:1)
myClass
。该程序不使用堆exept可能用于在后台分配stdout缓冲区。当myClass
超出范围时(例如,当main
返回或引发异常时),myClass
将被销毁并且堆栈被释放,导致p
无效。
在现代C ++中,使用智能指针(例如shared_ptr)而不是原始指针被认为更安全。
要在堆上分配myClass
,您可以写:
#include <memory>
int main() {
std::shared_ptr<MyClass> p (new MyClass (100)); // Two heap allocations: for reference counter and for MyClass.
auto p2 = std::make_shared<MyClass> (101); // One heap allocation: reference counter and MyClass stored together.
return 0;
}
答案 3 :(得分:1)
您在堆栈上创建了myClass对象和指针p(声明为MyClass *)。所以。在myClass对象中,我也在堆栈上创建数据成员作为myClass对象的一部分。指针p存储在堆栈中,但您没有为p分配,而是将p分配给已存储在堆栈中的myClass对象的地址。所以,这里没有堆分配,至少来自你发布的程序段。