关于在内存中组织c ++程序:在内存中组织c ++程序 – 堆栈和堆

Organization of a c++ program in memory - stack and heap

本问题已经有最佳答案,请猛点这里访问。

我正在学习C++,想知道这样的程序是如何在主内存中组织的。我知道有一个堆栈(带有stackframes)和一个堆。我知道动态分配的东西会在堆上分配它。这是由像mallocnew这样的操作员完成的。但是在这个小型C++程序中我看不到它们。

程序由一个主类和一个名为MyClass的类组成。本课程有:

  • 一个构造函数
  • 一个成员变量(int)
  • 一个成员函数

主类将对象定义为MyClass,并定义指向该对象的指针。

那么-这些记忆是如何组织起来的?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#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;
}


让我们一行一行地穿过这条线。

1
int main() {

一个新的函数从这一行开始,然后是一个新的范围。

1
    MyClass myClass(100), *p;

这里发生了两件事。第一,变量myClass在函数的作用域内声明,使其成为局部变量,因此它被分配到堆栈上。编译器将发出在堆栈上保留足够空间的机器指令(通常通过碰撞sp堆栈指针寄存器),然后执行对类构造函数的调用。传递给构造函数的this指针是堆栈分配的基础。

第二个变量p只是一个本地指针,编译器(取决于优化)可以将该值存储在本地堆栈或寄存器中。

1
   cout << myClass.get_nmbr() << endl;

调用本地myClass实例的get_nmbr()方法。同样,this指针指向本地堆栈帧分配。此函数查找实例变量i的值,并将其返回给调用方。注意,由于对象是在堆栈帧上分配的,所以i也存在于堆栈帧上。

1
   p = &myClass;

myClass实例的地址存储在变量p中。这是堆栈地址。

1
2
3
   cout << p;
   return 0;
}

打印出局部变量p并返回。

您的所有代码都只与堆栈分配有关。这样做的结果是,当函数的作用域在执行时保持/关闭(例如函数返回)时,对象将自动"销毁"并释放内存。如果您从该函数返回了像p这样的指针,那么您将看到一个悬空指针,即指向已释放和销毁的对象的指针。(根据语言标准,通过悬空指针访问内存的行为是"未定义的"。)

如果要在堆上分配对象,因此将其生命周期扩展到其声明的范围之外,则使用C++中的新运算符。在引擎盖下,new调用malloc,然后调用适当的构造函数。

您可以将上面的示例扩展到如下内容:

1
2
3
4
5
6
7
8
9
10
{
    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
"
, 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.
}

注意:您可能想看看新的位置,也许在这个上下文中是自动指针和共享指针。


第一件事。在C++中,不应该使用EDCOX1×0。

在这个程序中,所有使用的内存都在堆栈上。让我们一次看一个:

1
MyClass myClass(100);

myClass是堆栈上大小等于sizeof(MyClass);的自动变量。这包括成员i

1
MyClass *p;

p是堆栈上指向myClass实例的自动变量。因为它没有初始化,所以可以指向任何地方,不应该使用。它的大小等于sizeof(MyClass*);,可能(但不一定)就放在myClass之后的堆栈上。

1
cout << myClass.get_nmbr() << endl;

MyClass::get_nmbr()返回myClass实例的成员i的副本。这个拷贝可能被优化掉了,因此不会占用任何额外的内存。如果是这样,它就在堆栈上。ostream& operator<< (int val);的行为是特定于实现的。

1
p = &myClass;

这里,p指向一个有效的myClass实例,但是由于该实例已经存在,所以内存布局没有任何变化。

1
cout << p;

输出myClass的地址。operator<<的行为再一次是特定于实现的。

这个程序(有点)可视化自动变量的布局。


您在堆栈上创建了MyClass对象和指针P(声明为MyClass*)。所以。在MyClass对象中,作为MyClass对象的一部分,我也在堆栈上创建了数据成员。指针P存储在堆栈上,但您没有为P分配,而是将P分配给已存储在堆栈上的MyClass对象的地址。因此,这里没有堆分配,至少从您发布的程序段中没有。


在堆栈上分配myClass。这个程序不使用heap exept来分配后台的stdout缓冲区。当myClass超出作用域时(如main返回或抛出异常),myClass被破坏,堆栈被释放,使p无效。

在现代C++中,使用智能指针(如SyrdY-PTR)代替原始指针被认为是安全的。要在堆上分配myClass,您可以编写:

1
2
3
4
5
6
7
#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;
}