关于编译器构造:C ++中的内存组织

Memory Organization in C++

我一直在阅读如何在C++中分配内存。

需要提及的一些资源:

http://www.geeksforgeks.org/memory-layout-of-c-program/

http://duartes.org/gustavo/blog/post/anatomy-of-a-program-in-memory

在堆栈/堆上创建对象?

在堆栈或堆中C++的全局内存管理?

http://msdn.microsoft.com/en-us/library/vstudio/dd293645.aspx

堆/堆栈和多个进程

不同的程序是从一个公共堆还是从一个单独的堆获取内存?

网址:http://computer.howtuffworks.com/c28.htm

enter image description here

我想根据我的阅读澄清一些要点:

根据http://www.geeksforgeks.org/memory-layout-of-c-program/第4节堆栈"存储自动变量的堆栈,以及每次调用函数时保存的信息"

假设:

1
2
3
4
5
6
7
8
9
10
class myClass{
  int a;
  char b;
  public:
  myClass(int a,char b)
  {
   this->a = a;
   this->b = b;
  }
};

1)根据我读到的,当我们编译这段代码时,二进制文件位于程序内存中,堆栈上还没有分配任何内容。对的?

现在我的主要任务是:

1
2
3
4
5
int main()
{
 myClass Ob(1,'c');
 return 0;
}

2)现在在堆栈上创建一个大小为5字节(4字节(int),1字节(char)-32位OS)的对象ob,因为它是一个自动变量。对的?

3)当调用构造函数myClass(int a,char b)时,是否在堆栈上为构造函数创建临时变量(参数a、b),然后在创建对象ob后销毁?就像我们通过按值传递参数来调用函数一样。

现在假设另一个班

1
2
3
4
5
6
7
8
9
class pointerClass {
 int a;
 char* b;
 public:
 pointerClass(int size){
 b= new char[size];
 a=size;
 }
};

现在主要是:

1
2
3
4
int main()
{
 pointerClass ptr(10) ; //Step 1
}

4)这是否意味着在堆栈上创建了大小为8字节(int a(4字节)、char*b(4字节,即只包含指向堆的地址)的ptr对象?另外,一个10字节的内存(对应于堆上分配的新char[10])是由char*b的内容指向的?我说的对吗?

5)当我们通过引用(如fn (int *a,char* b)fn(int& a,char& b)向函数传递参数时,这是否意味着在堆栈上为函数创建一个临时指针/引用,该函数指向函数返回时传递和销毁的实际对象?或者是传递实际对象,而不是在函数的堆栈上创建和销毁临时指针/引用?

这是我昨天问的,但我对答案不满意:构造函数、复制构造函数和堆栈创建:C++

6)当我们超载一个fn,如fn(int a,char b)fn(int& a,char& b)时,我们可以从main调用fn(A,B)。下铸static_cast(fn)(a, c); //Calls fn(int a,char b)static_cast(fn)(a, c);//Calls fn(int& a.char& b)这里到底发生了什么?什么是虚空(*)。

谢谢


  • 正确-分配发生在运行时。
  • 部分正确-标准不使用术语stack和heap,它只要求对象的行为。但是,堆栈是实现此功能的最常见和常用的方法。此外,编译器允许用填充字节填充结构,因此不应推测对象的大小。这被称为结构填充。简单地说,使用sizeof获得大小。
  • 部分正确-按值传递和返回需要一个复制构造函数才能访问调用,但在某些情况下,这些调用可能会被忽略。这个过程被称为副本删除。
  • 部分正确-指针只指向具有动态存储的对象。指针的大小可能会有所不同。
  • 指针或引用是在函数的本地创建的,但它指向或引用了其地址被传递的对象。这里不需要副本,也不会发生任何情况。
  • 每个变量在C和C++中都有数据类型。类型转换允许您灵活地强制编译器将指向一个数据类型的指针视为指向完全不同数据类型的指针。由于函数有一个类型,指向函数的指针也有一个类型,并且类型转换允许您强制编译器将函数指针从一个函数类型处理为完全另一个类型,因此本质上允许您调用所需的重载函数版本。

  • 对的
  • 正确(尽管可能不是5个字节,可能是8个字节)
  • 对的
  • 对的
  • 临时指针/引用是在堆栈上创建的,不知道您为什么对前面给出的答案不满意,我觉得它是正确的。
  • void(*)(int,char)是一个类型,特别是指向一个带有两个参数和int和char的void函数的指针。显然,这种类型转换强制编译器选择所需的函数版本,尽管这对我来说是可能的。
  • 当然,必须添加强制性警告,上面没有什么是C++所需要的,它只是C++是如何实现的。


    首先,我要指出的是,你展示的图表取决于系统。例如,在Solaris下,操作系统内存根本没有映射到用户地址空间。最频繁的映射只有三个映射段用户存储器,至少在程序的开头:一个代码在底部分段(但不是绝对底部,因为地址0通常不会被映射),它上面的数据段,顶部有一个堆栈段(向下生长),其中堆栈和数据之间的未映射内存孔。

    所有这些都会在您动态启动时完全改变加载。

  • 不,在你完成一个代码之后编译),可执行文件在一个文件中,而不是在内存中。这个程序在执行之前不会加载到内存中。(以前在早期的Unix和Embedded中有一些异常系统,即使是今天。但对于一般用途的系统Windows和现代Unix,这是真的。)

  • 变量将在堆栈上创建。但它几乎会由于对齐,肯定大于5字节注意事项。(最多32个字节最少8个字节在C++中,对象创建是一个两步过程:内存分配和调用构造函数。在大多数实现,需要的所有内存将分配函数中使用的所有对象立即,在函数的顶部;在某些情况下,额外内存也将分配到每个变量的任一侧,由于调试原因,内存将被初始化。程序流时将调用对象的构造函数传递对象的定义。

  • 对。调用构造函数与调用任何其他功能。同样,创建参数是一个两步过程;在这种情况下,策略是不同的:一些实现将分配顶部任何参数所需的所有内存其他人将根据需要在在初始化它们之前。在简单的情况下变量,像int一样,大多数机器只有一条指令允许在同一个指令。取决于参数的类型,以及如何它们被初始化,编译器甚至可以使用不同的策略。

  • 正确,或多或少。对于内置类型,如int或指针,唯一的"破坏"是释放内存,以及根据编译器的不同,这可能要到最后才会发生调用函数的。(另一方面,如果你打电话第二个函数,这个内存将被重用。所以这个程序其行为完全"好像"内存已被立即释放。)

  • 正确,或多或少。(正式地说,参考文献不是"销毁",因为它们不是物体。实际上,至少当它们用作函数参数时,基础代码与指针完全相同。)

  • 首先,你唯一能做的就是指向函数的指针上的static_cast将其强制转换回它的原始类型。其他的都是未定义的行为。如果fn定义为void fn( int a, char b ),使用static_cast( fn )未定义行为,而且不起作用。这里到底发生了什么几乎什么都可以,但很有可能程序崩溃。在这种情况下,void (*)是指向函数类型的指针的声明;void (*)( int, char
    )
    是该类型的名称:指向函数的指针(*)( int, char )—括号是必要的,因为优先规则)返回void

  • 编辑:

    只是关于第6点的修正。我错过了这样一个事实两种类型的函数都被重载。一个static_cast罐用于解决函数重载,如下所示:案例,通常的规则不适用,因为没有类型转换。(是的,这很混乱。)