3

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

需要提及的一些资源:

http://www.geeksforgeeks.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.howstuffworks.com/c28.htm

在此处输入图像描述

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

根据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 字节 (int),1 字节 (char) - 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 字节(int a(4 字节)、char* b(4 字节,即它只是保存指向堆的地址)的 ptr 对象?此外还有 10 字节的内存(对应于new 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) 下面的 cast 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) 到底发生了什么?什么是 void (*) 。

谢谢

4

3 回答 3

6
  1. 正确 - 分配发生在运行时。
  2. 部分正确 - 该标准不使用术语堆栈和堆,它仅要求对象的行为。但是,堆栈是实现此功能的最常见和最常用的方法。此外,允许编译器使用填充字节填充结构,因此不应推测对象的大小。这称为结构填充。简单地说,sizeof用来获取大小。
  3. 部分正确 - 按值传递和返回确实需要复制构造函数才能访问调用,但这些调用可能会在某些情况下省略。该过程称为复制省略
  4. 部分正确 - 指针仅指向具有动态存储的对象。指针的大小可能会有所不同。
  5. 指针或引用是在函数本地创建的,但它指向或引用其地址被传递的对象。这里不需要副本,也不会发生任何事情。
  6. 在 C 和 C++ 中,每个变量都有一个数据类型。类型转换允许您灵活地强制编译器将指向一种数据类型的指针视为指向完全不同数据类型的指针。由于函数具有类型,指向函数的指针也具有类型,并且类型转换允许您强制编译器将函数指针从一种函数类型完全处理为另一种类型,从而基本上允许您调用所需的重载函数版本。
于 2013-05-07T06:27:07.477 回答
4
  1. 正确的
  2. 正确(虽然可能不是五个字节,可能是八个)
  3. 正确的
  4. 正确的
  5. 临时指针/引用是在堆栈上创建的,不知道为什么您对之前给出的答案不满意,它对我来说看起来是正确的
  6. void(*)(int,char)是一种类型,特别是指向带有两个参数和一个 int 和一个 char 的 void 函数的指针。显然,这种强制转换迫使编译器选择您想要的函数版本,尽管这对我来说是可能的新闻。

当然必须添加强制性警告,即 C++ 不需要上述任何内容,这正是 C++ 通常实现的方式。

于 2013-05-07T06:20:11.303 回答
3

首先,我应该指出您显示的图表非常依赖于系统。例如,在 Solaris 下,操作系统内存根本没有映射到用户地址空间。而最频繁的映射只有三个用于用户内存的映射段,至少在程序开始时是这样:底部的代码段(但不是绝对底部,因为通常不会映射地址 0),数据段在它上面,在顶部有一个堆栈段(向下增长),在堆栈和数据之间有一个未映射的内存大洞。

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

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

  2. 该变量将在堆栈上创建。但由于对齐方面的考虑,它几乎肯定会大于 5 个字节。(对于大多数 32 位机器来说,最少 8 个字节。)在 C++ 中,对象创建是一个两步过程:内存分配和调用构造函数。在大多数实现中,函数中使用的所有对象所需的所有内存都将立即在函数顶部分配;在某些情况下,出于调试原因,还会在每个变量的任一侧分配额外的内存,并且内存将被初始化。当程序流通过对象的定义时,将调用对象的构造函数。

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

  4. 正确的,或多或少。对于像或指针这样的内置类型int,唯一的“破坏”是释放内存,并且取决于编译器,这可能直到调用函数结束时才会发生。(另一方面,如果您调用第二个函数,该内存将被重用。因此程序的行为完全“好像”内存已被立即释放。)

  5. 正确的,或多或少。(形式上,引用不会“销毁”,因为它们不是对象。实际上,至少当它们用作函数参数时,底层代码与指针完全相同。)

  6. 首先,您可以对指向函数的指针的 a 的结果做的唯一事情static_cast就是将其转换回其原始类型。其他任何事情都是未定义的行为。如果 fn定义为void fn( int a, char b ),则使用 的结果 static_cast<void (*) ( int&, char& )>( fn )是未定义的行为,并且将不起作用。这里发生的事情几乎可以是任何事情,但它很有可能会使程序崩溃。void (*)在这种情况下,是函数类型指针声明的一部分void (*)( int, char ), 是类型的名称:指向*函数的指针(the)(the ( int, char )— 括号是必要的,因为优先级规则)返回void

编辑:

只是对第 6 点的更正。我错过了这两种类型的函数都重载的事实。Astatic_cast可用于像这样解决函数重载:在这种情况下,通常的规则不适用,因为没有类型转换。(是的,这非常令人困惑。)

于 2013-05-07T08:55:54.380 回答