我一直在阅读如何在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
我想根据我的阅读澄清一些要点:
根据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)这里到底发生了什么?什么是虚空(*)。
谢谢
- 我认为你需要把这个问题分成几个小问题。你希望我们写C++书作为答案吗?
- 我认为所有的问题都是相关的,因此把它们放在一个问题下。答案主要是"是"或"否"。所以在回复时不需要输入太多。
- @Mat编辑了我的问题
- 如果你问我的话,也不是什么编程问题。只是一个"我认为这样行,行吗"的问题。根据约翰的回答,他在所有问题上似乎都是正确的。
- 我认为C++(作为标准定义的语言)与计算机的内存模型没有紧密耦合,程序运行在计算机上。例如,在C++中定义变量并不一定意味着栈上有任何改变。
- "或者更确切地说,传递的是实际对象"该如何工作?传递参数基于将它们复制到相对于堆栈指针(帧指针IIRC)的预定义堆栈位置。
- 这真的不太适合这样。所以是针对具体的实际编程问题,而不是从一开始就学习操作系统和语言设计。
正确-分配发生在运行时。
部分正确-标准不使用术语stack和heap,它只要求对象的行为。但是,堆栈是实现此功能的最常见和常用的方法。此外,编译器允许用填充字节填充结构,因此不应推测对象的大小。这被称为结构填充。简单地说,使用sizeof获得大小。
部分正确-按值传递和返回需要一个复制构造函数才能访问调用,但在某些情况下,这些调用可能会被忽略。这个过程被称为副本删除。
部分正确-指针只指向具有动态存储的对象。指针的大小可能会有所不同。
指针或引用是在函数的本地创建的,但它指向或引用了其地址被传递的对象。这里不需要副本,也不会发生任何情况。
每个变量在C和C++中都有数据类型。类型转换允许您灵活地强制编译器将指向一个数据类型的指针视为指向完全不同数据类型的指针。由于函数有一个类型,指向函数的指针也有一个类型,并且类型转换允许您强制编译器将函数指针从一个函数类型处理为完全另一个类型,因此本质上允许您调用所需的重载函数版本。
- "指针的大小可能会有所不同。"可能取决于C++实现(即编译器、目标平台等),但在编译程序中是恒定的。
- @Juanchopanza:因此第3点的部分正确。澄清,以消除令人困惑的措辞。
- 我喜欢这个答案,所以+1,但是我有一些评论:"分配发生在运行时",有加载时间和库所需的结构初始化(例如一些有状态的C字符串函数)。我问"动态内存"是因为我只知道标准的"动态存储持续时间"(->new,delete),但是指针也可以指向静态或自动(或线程本地)存储持续时间的对象。6)过载分辨率需要转换。
- 注(3):没有要求在堆栈上传递参数。许多处理器在寄存器中传递前n个参数。
- 最后一句话听起来像是执行ub的reinterpret_cast。对于转换来说,为重载的函数名选择重载是一个默认用例,而不是间接影响IMO。
- 回复2:本标准在此上下文中可能不使用"堆栈"一词,但它确实需要一个堆栈。(如何实现堆栈是另一个问题:我曾经使用过C的实现,它通过一个链表实现,每个堆栈帧都分配在堆栈上。)
对的
正确(尽管可能不是5个字节,可能是8个字节)
对的
对的
临时指针/引用是在堆栈上创建的,不知道您为什么对前面给出的答案不满意,我觉得它是正确的。
void(*)(int,char)是一个类型,特别是指向一个带有两个参数和int和char的void函数的指针。显然,这种类型转换强制编译器选择所需的函数版本,尽管这对我来说是可能的。
当然,必须添加强制性警告,上面没有什么是C++所需要的,它只是C++是如何实现的。
- 关于第一个问题:我想说,这在很大程度上取决于你如何定义"堆栈"和其他一些术语。1)根据我读到的,当我们编译这段代码时,二进制文件位于程序内存中,堆栈上还没有分配任何内容。"编译!=加载程序(或执行)等
- 厕所。。关于5:我得到的答复不是临时引用,或者指针是在堆栈上创建的,而是直接传递对象…如果你不知道的话,你能给我一些参考资料吗?
- @Gauravk我认为你误解了答案"在任何一种情况下,物体的复制品都不是被创造出来的。"(我的重点)。
- @ john Ok。这意味着创建了指向传递对象的临时引用/指针,并且在堆栈上没有创建对象的副本。这也是为什么不调用复制构造函数的原因…
- @Gauravk正确
首先,我要指出的是,你展示的图表取决于系统。例如,在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罐用于解决函数重载,如下所示:案例,通常的规则不适用,因为没有类型转换。(是的,这很混乱。)
- +1对于这个全面而精确的答案。
- @是的,是打字错误。