C ++中的内存组织

时间:2013-05-07 06:13:11

标签: c++ compiler-construction

我一直在阅读有关如何在C ++中分配内存的信息。

要提及的一些资源:

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

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

Object creation on the stack/heap?

Global memory management in C++ in stack or heap?

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

Heap / Stack and multiple processes

Do different programs gets their memory from a common heap or from a separate heap?

http://computer.howstuffworks.com/c28.htm

enter image description here

我想根据我的阅读澄清几点:

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

假设:

class myClass{
  int a;
  char b;
  public:
  myClass(int a,char b)
  {
   this->a = a;
   this->b = b;
  } 
};

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

现在在我的主要:

int main()
{
 myClass Ob(1,'c');
 return 0;
} 

2)现在,在堆栈上创建了一个大小为5字节(4字节(整数),1字节(字符) - 32位OS)的对象Ob,因为它是一个自动变量。正确吗?

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

现在假设另一个班级

class pointerClass {
 int a;
 char* b;
 public:
 pointerClass(int size){
 b= new char[size];
 a=size;
 }
}; 

现在主要:

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

4)这是否意味着大小为8字节的ptr对象(int a(4字节),char * b(4字节,即这只是持有指向堆的地址)是在堆栈上创建的?还有10字节的内存(对应于新的char [10]在堆上分配)由char * b的内容指向?我是否正确?

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

我昨天问过,但我对答案不满意: Constructor, Copy Constructor and Stack Creation : C++

6)当我们重载诸如fn(int a,char b) fn(int& a,char& b)之类的fn时,我们可以从main调用fn(A,B) 以下演员 static_cast<void(*)(int, char)>(fn)(a, c); //Calls fn(int a,char b) static_cast<void(*)(int&, char&)>(fn)(a, c);//Calls fn(int& a.char& b) 到底发生了什么?什么是空(*)。

由于

3 个答案:

答案 0 :(得分:6)

  1. 正确 - 分配在运行时发生。
  2. 部分正确 - 标准不使用术语堆栈和堆,它只需要来自对象的行为。但是,堆栈是实现此功能的最常见和最常用的方式。此外,允许编译器使用填充字节填充结构,因此不应推测对象的大小。这称为 structure padding 。只需使用sizeof即可获得尺寸。
  3. 部分正确 - 按值传递和返回确实需要复制构造函数作为可访问的调用,但这些调用可能在某些情况下被省略。该流程称为 copy elision
  4. 部分正确 - 指针指向具有动态存储的对象。指针的大小可能会有所不同。
  5. 指针或引用是在函数本地创建的,但它指向或引用传递地址的对象。此处不需要复制,也不会发生任何副本。
  6. 每个变量在C和C ++中都有数据类型。类型转换允许您灵活地强制编译器将指向一个数据类型的指针视为指向完全不同的数据类型的指针。由于函数有一个类型,函数的指针也有一个类型,并且类型转换允许您强制编译器将函数指针从一个函数类型处理为完全另一个类型,因此基本上允许您调用所需的重载函数版本。

答案 1 :(得分:4)

  1. 正确
  2. 正确(虽然它可能不是五个字节,可能是八个字节)
  3. 正确
  4. 正确
  5. 在堆栈上创建了临时指针/引用,不确定为什么你对之前给出的答案感到不满意
  6. void(*)(int,char)是一个类型,特别是指向带有两个参数和int和char的void函数的指针。很明显,这个演员会强制编译器选择你想要的函数的版本,虽然这对我来说是可能的。
  7. 当然必须添加强制性警告,即C ++中不需要上述任何内容,而是通常如何实现C ++。

答案 2 :(得分:3)

首先,我应该指出你所展示的图表是非常的 系统依赖。例如,Solaris下的操作 系统内存根本没有映射到用户地址空间。 最常见的映射只有三个映射段 用户内存,至少在程序的开头:代码 在底部(但不是绝对底部),因为 地址0通常不会被映射),它上面的数据段, 并且在顶部有一个堆叠段(长大),有一个大的 堆栈和数据之间未映射的内存空洞。

一旦动态启动,所有这些都会完全改变 加载。

  1. 没有。完成代码后(在更大的意义上) 编译),可执行文件在文件中,而不在内存中。该 程序在执行之前不会加载到内存中。 (在早期的Unix和嵌入式系统中曾经存在一些例外 系统,即使在今天。但对于像。这样的通用系统 Windows和现代Unix,这是真的。)

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

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

  4. 正确,或多或少。对于内置类型,如int或 指针,唯一的“破坏”就是释放记忆,而且 取决于编译器,可能直到最后才会发生 调用函数。 (另一方面,如果你打电话 第二个功能,这个内存将被重用。所以该计划 行为完全“好像”记忆已立即释放。)

  5. 正确,或多或少。 (形式上,参考文献不是 “被摧毁”,因为它们不是物体。实际上,至少 当它们被用作函数参数时,底层代码 与指针完全相同。)

  6. 首先,你可以用 来完成结果 指向函数的指针上的static_cast将其强制转换为 它的原始类型。其他任何东西都是未定义的行为。如果 使用{的结果将fn定义为void fn( int a, char b ) static_cast<void (*) ( int&, char& )>( fn )未定义 行为,将无法正常工作。这里究竟发生了什么 几乎任何东西,但它很有可能 程序崩溃。在这种情况下,void (*)部分 声明函数类型的指针; void (*)( int, char ),是要使用的类型:指针(*)的名称( ( int, char ) - 因为括号是必要的 优先规则)返回void

  7. 编辑:

    只是对第6点进行了修正。我错过了这个事实 两种类型的函数都被重载了。一个static_cast可以 用于解决这样的函数重载:在这样的 例如,通常的规则不适用,因为没有类型 转换。 (是的,这非常令人困惑。)