C++程序如何分配内存

3

考虑下面的程序:

#include <string>
#include <iostream>

class B {
  private:
    std::string s;

  public:
    B() { s = fun(); }
    std::string fun() { return "hello"; }
    void print() {
        std::cout << s;
    }
};

int main(){
    B b;
    b.print();
}

输出是Hello

我的问题如下:

  1. 数据成员(在此情况下为's')的内存分配顺序是什么。
  2. 在构造函数中调用fun()时,对象是否存在。

我怀疑我如何在构造函数之前调用b对象上的函数。


1
我的疑惑是如何在构造函数中调用“b”对象上的函数,而该对象尚未创建。什么让你认为它没有被创建? - πάντα ῥεῖ
2
为了完整性,也考虑这个替代方案:B():s(fun()) {} - rustyx
3
1)C++标准没有定义“stack”或“heap”。这只是一种实现细节,从技术上讲,在编译器之间可能会有所不同。 2)“全局数据是在堆上创建的。”是什么让你这么说? - Algirdas Preidžius
2
@SPlatten - C语言也没有定义它... - StoryTeller - Unslander Monica
1
@SPlatten 不是这样的。而且,也不是那样的。C++标准只定义了“存储期”,以及每个存储期的预期行为。它的具体实现方式取决于特定的编译器。如果您确定它定义了堆栈和堆(而不是谈论std::stack和家族),请在标准中提供一个章节。 - Algirdas Preidžius
显示剩余4条评论
4个回答

5
在对象的构造函数体开始执行时,所有对象的基类、直接或间接的成员都已经被显式或隐式地初始化。因此,s 是一个有效的字符串对象,可以合法地作为赋值运算符左侧。
需要注意的一点是,如果您从构造函数中调用多态类的虚方法,则会选择当前类型的实现,因为任何派生类型尚未初始化,因此如果有任何重载,则调用它们将是非法的。

0
  1. 在这种情况下,数据成员('s')的内存分配顺序是什么。

对象总是存储在一个连续的内存块中,无法调整大小。因此,当超级对象被分配时,所有子对象都同时被分配。

请注意,像 std::string 这样的对象也可能分配一个与对象本身的内存不同的动态内存缓冲区。最早可以分配这个内存的时间是在字符串的构造函数中。也就是说,空字符串不需要缓冲区,因此可以延迟分配直到以后。

  1. 在构造函数调用fun()时,对象是否存在。

不完全存在。在构造函数之前,它的所有内存都已经存在,并且所有子对象都将在对象的构造函数之前完全构造。但是,对象的生命周期始于构造函数完成时。也就是说:

我的疑问是,我如何在构造函数尚未创建b对象的情况下调用b对象上的函数。

在构造函数内部调用成员函数是可以的。

你只需要确保不要做任何依赖于构造函数已经执行的事情,因为它尚未被执行。例如,如果您指定了一个类不变式,那么该不变式是由构造函数建立的,那么在建立不变式之前,您就不能调用任何依赖于该不变式的函数。

请注意,成员函数也可以从成员初始化列表中调用。在那里,一些子对象尚未初始化,因此必须非常小心,不要调用访问未初始化子对象的这些成员函数。


0
回答您的问题,让我们参考CPP标准(N4713)。 请看下面的突出部分:
  1. 在哪个顺序为数据成员(在这种情况下为's')分配内存。
  2. 在构造函数调用fun()时,对象是否存在。
15.6.2 初始化基类和成员
... 13. 在非委托构造函数中,初始化按照以下顺序进行: (13.1) - 首先,仅对于最派生类(6.6.2)的构造函数,虚拟基类按照它们在基类列表的深度优先从左到右遍历的顺序进行初始化,其中“从左到右”是基类在派生类基础说明符列表中出现的顺序。 (13.2) - 然后,直接基类按照它们在基础说明符列表中出现的声明顺序进行初始化(不考虑 mem-initializers 的顺序)。 (13.3) - 然后,非静态数据成员按照它们在类定义中声明的顺序进行初始化(同样不考虑 mem-initializers 的顺序)。 (13.4) - 最后,执行构造函数体的复合语句。 (13.3) 回答了问题 1。 (13.4) 回答了问题 2。在进入构造函数体时,所有非静态数据成员都已经被初始化。

0
我的疑问是如何在构造函数还未创建b对象时调用其函数。
它已经被创建了。
所有成员都在构造函数体开始之前创建。
否则,构造函数体将毫无意义!
从哲学的角度来看,这就是为什么我们永远不应该说B b“调用了构造函数”(当您创建临时对象时尤其重要,比如B(),它有点像调用函数/构造函数)……因为虽然构造函数显然涉及到,但其他事情也会发生,并且其中一些/大部分会先发生。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接