2013-05-07 68 views
2

我一直在阅读如何在C++中分配内存。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栈“堆栈,其中自动存储变量以及每次保存的信息E中的功能被称为”

假设:

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个字节的物体Ob(4字节(int)的,1个字节(字符) - 32位OS)是在堆叠产生的,因为它是一个自动变量。正确吗?

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一个(4个字节),字符* 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如fn(int a,char b)fn(int& a,char& b)我们可以从主调用为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) 到底发生了什么?什么是无效(*)。

由于

+7

我认为你需要将这个问题分成几个小问题。你希望我们写C++书作为答案吗? – 2013-05-07 06:16:44

+0

我认为所有的问题都是相关的,因此置于一个问题之下。答案是“是”或“否”。大多数情况下,输入时不需要输入太多的内容。 – 2013-05-07 06:18:09

+0

@Mat编辑我的问题 – 2013-05-07 06:19:48

回答

4
  1. 正确
  2. 正确(尽管它可能不是五个字节,大概为八个)
  3. 正确
  4. 正确
  5. 暂时指针/参考在堆栈上创建的,而不是肯定你为什么不满意之前给出的答案,它看起来对我来说是正确的
  6. void(*)(int,char)是一种类型,sp特别指向一个带有两个参数和int和一个char的void函数的指针。很显然,这个演员强制编译器选择你想要的函数的版本,虽然这对我来说可能是新闻。

当然必须添加必要的警告,上面的任何内容都不需要C++,这只是C++的典型实现方式。

+0

关于第一个问题:我认为这在很大程度上取决于你如何定义“堆栈”和其他一些术语。 “1)根据我所读到的,当我们编译这段代码时,二进制文件位于程序存储器中,而且还没有任何文件被分配到堆栈中。”编译!=加载程序(或执行)等 – dyp 2013-05-07 06:22:17

+0

约翰。关于5:我得到的答复是没有临时引用或指针创建在堆栈上,而是直接传递对象...你能通过我的任何引用阅读如果你为此编了任何东西? – 2013-05-07 06:24:43

+2

@GauravK我认为你误解了答案'在任何一种情况下,对象的副本都是**而不是**创建的。' (我强调)。 – john 2013-05-07 06:26:29

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

“尽管指针的大小可能会有所不同。”可以根据C++实现(即编译器,目标平台等)而变化,但是在编译的程序中是不变的。 – dyp 2013-05-07 06:34:04

+0

@juanchopanza:因此,*部分正确*在第3点。澄清它,以消除混淆的措辞。 – 2013-05-07 06:35:53

+0

那么我喜欢这个答案,所以+1,但我有一些评论:“分配发生在运行时”有加载时间和库所需的结构的初始化(例如一些有状态的C字符串函数)。我问了关于“动态内存”,因为我只知道Std的“动态存储持续时间”( - > new,delete),但是指针也可以指向静态或自动(或线程本地)存储持续时间的对象。 6)重载分辨率需要转换。 – dyp 2013-05-07 06:42:35

3

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

只要动态启动 加载,所有这些都会完全更改。

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

  2. 变量将在创建叠加。但由于考虑到对齐 的原因,几乎 肯定会大于5个字节。 (8个字节对于大多数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

编辑:

只是一个关于6点我已经错过了 功能超载对于两种类型的事实修正。 A static_cast可以使用 来解决函数重载,例如:在这样的 的情况下,通常的规则不适用,因为没有类型 转换。 (是的,这很混乱。)

+0

+1这个全面而准确的答案。 – dyp 2013-05-07 13:07:52

+0

@DyP错字,是的。 – 2013-05-07 13:07:52