c++中c和c 的内存分配方式式有几种

内存的分配方式的分配方式有几种? - C++问答 - 大学IT网
当前位置: >
> 内存的分配方式的分配方式有几种?
内存的分配方式的分配方式有几种?
一、从静态存储区域分配。内存在程序编译的时候就已经分配好,这块内存在程序的
整个运行期间都存在。例如全局变量。
二、在栈上创建。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执
行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高
,但是分配的内存容量有限。
三、从堆上分配,亦称动态内存分配。程序在运行的时候用malloc或new申请任意多少的内存,程序员自己负责在何时用free或delete释放内存。动态内存的生存期由我们决定,使用非常灵活,但问题也最多。
其他类似问题书上类继承相关章节到这里就结束了,这里不妨说下C++内存分布结构,我们来看看编译器是怎么处理类成员内存分布的,特别是在继承、虚函数存在的情况下。
工欲善其事,必先利其器,我们先用好Visual Studio工具,像下面这样一步一步来:
先选择左侧的C/C++-&命令行,然后在其他选项这里写上/d1 reportAllClassLayout,它可以看到所有相关类的内存布局,如果写上/d1 reportSingleClassLayoutXXX(XXX为类名),则只会打出指定类XXX的内存布局。近期的VS版本都支持这样配置。
下面可以定义一个类,像下面这样:
1 class Base
void CommonFunction();
然后编译一下,可以看到输出框里面有这样的排布:
这里不想花精力在内存对齐因素上,所以成员变量都设为int型。
从这里可以看到普通类的排布方式,成员变量依据声明的顺序进行排列(类内偏移为0开始),成员函数不占内存空间。
再看下继承,往后面添加如下代码:
1 class DerivedClass: public Base
void DerivedCommonFunction();
编译,然后看到如下的内存分布(父类的内存分布不变,这里只讨论子类成员变量的内存分布):
可以看到子类继承了父类的成员变量,在内存排布上,先是排布了父类的成员变量,接着排布子类的成员变量,同样,成员函数不占字节。
下面给基类加上虚函数,暂时注释掉DerivedClass,看一下这时的内存排布:
1 class Base
void CommonFunction();
void virtual VirtualFunction();
这个内存结构图分成了两个部分,上面是内存分布,下面是虚表,我们逐个看。VS所带编译器是把虚表指针放在了内存的开始处(0地址偏移),然后再是成员变量;下面生成了虚表,紧跟在&Base1_meta后面的0表示,这张虚表对应的虚指针在内存中的分布,下面列出了虚函数,左侧的0是这个虚函数的序号,这里只有一个虚函数,所以只有一项,如果有多个虚函数,会有序号为1,为2的虚函数列出来。
编译器是在构造函数创建这个虚表指针以及虚表的。
那么编译器是如何利用虚表指针与虚表来实现多态的呢?是这样的,当创建一个含有虚函数的父类的对象时,编译器在对象构造时将虚表指针指向父类的虚函数;同样,当创建子类的对象时,编译器在构造函数里将虚表指针(子类只有一个虚表指针,它来自父类)指向子类的虚表(这个虚表里面的虚函数入口地址是子类的)。
所以,如果是调用Base *p = new Derived();生成的是子类的对象,在构造时,子类对象的虚指针指向的是子类的虚表,接着由Derived*到Base*的转换并没有改变虚表指针,所以这时候p-&VirtualFunction,实际上是p-&vfptr-&VirtualFunction,它在构造的时候就已经指向了子类的VirtualFunction,所以调用的是子类的虚函数,这就是多态了。
下面加上子类,并在子类中添加虚函数,像下面这样:
1 class DerivedClass: public Base
void DerivedCommonFunction();
void virtual VirtualFunction();
可以看到子类内存的排布如下:
上半部是内存分布,可以看到,虚表指针被继承了,且仍位于内存排布的起始处,下面是父类的成员变量a和b,最后是子类的成员变量c,注意虚表指针只有一个,子类并没有再生成虚表指针了;下半部的虚表情况与父类是一样的。
我们把子类换个代码,像这样:
1 class DerivedClass1 : public Base
void DerivedCommonFunction();
void virtual VirtualFunction2();
注意到这时我们并没有覆写父类的虚方法,而是重声明了一个新的子类虚方法,内存分布如下:
还是只有一个虚表指针,但是下方虚表的内容变化了,虚表的0号是父类的VirtualFunction,而1号放的是子类的VirtualFunction2。也就是说,如果定义了DerivedClass的对象,那么在构造时,虚表指针就会指向这个虚表,以后如果调用的是VirtualFunction,那么会从父类中寻找对应的虚函数,如果调用的是VirtualFunction2,那么会从子类中寻找对应的虚函数。
我们再改造一下子类,像这样:
1 class DerivedClass1 : public Base
void DerivedCommonFunction();
void virtual VirtualFunction();
void virtual VirtualFunction2();
我们既覆写父类的虚函数,也有新添的虚函数,那么可以料想的到,是下面的这种内存分布:
下面来讨论多重继承,代码如下:
1 class Base
void CommonFunction();
void virtual VirtualFunction();
11 class DerivedClass1: public Base
14 public:
void DerivedCommonFunction();
void virtual VirtualFunction();
19 class DerivedClass2 : public Base
22 public:
void DerivedCommonFunction();
void virtual VirtualFunction();
27 class DerivedDerivedClass : public DerivedClass1, public DerivedClass2
30 public:
void DerivedDerivedCommonFunction();
void virtual VirtualFunction();
内存分布从父类到子类,依次如下:
Base中有一个虚表指针,地址偏移为0
DerivedClass1继承了Base,内存排布是先父类后子类。
DerivedClass2的情况是类似于DerivedClass1的。
下面我们重点看看这个类DerivedDerivedClass,由外向内看,它并列地排布着继承而来的两个父类DerivedClass1与DerivedClass2,还有自身的成员变量e。DerivedClass1包含了它的成员变量c,以及Base,Base有一个0地址偏移的虚表指针,然后是成员变量a和b;DerivedClass2的内存排布类似于DerivedClass1,注意到DerivedClass2里面竟然也有一份Base。
这里有两份虚表了,分别针对DerivedClass1与DerivedClass2,在&DerivedDericedClass_meta下方的数字是首地址偏移量,靠下面的虚表的那个-16表示指向这个虚表的虚指针的内存偏移,这正是DerivedClass2中的{vfptr}在DerivedDerivedClass的内存偏移。
如果采用虚继承,像下面这样:
1 class DerivedClass1: virtual public Base
void DerivedCommonFunction();
void virtual VirtualFunction();
9 class DerivedClass2 : virtual public Base
12 public:
void DerivedCommonFunction();
void virtual VirtualFunction();
17 class DerivedDerivedClass :
public DerivedClass1, public DerivedClass2
20 public:
void DerivedDerivedCommonFunction();
void virtual VirtualFunction();
Base类没有变化,但往下看:
DerivedClass1就已经有变化了,原来是先排虚表指针与Base成员变量,vfptr位于0地址偏移处;但现在有两个虚表指针了,一个是vbptr,另一个是vfptr。vbptr是这个DerivedClass1对应的虚表指针,它指向DerivedClass1的虚表vbtable,另一个vfptr是虚基类表对应的虚指针,它指向vftable。
下面列出了两张虚表,第一张表是vbptr指向的表,8表示{vbptr}与{vfptr}的偏移;第二张表是vfptr指向的表,-8指明了这张表所对应的虚指针位于内存的偏移量。
DerivedClass2的内存分布类似于DerivedClass1,同样会有两个虚指针,分别指向两张虚表(第二张是虚基类表)。
下面来仔细看一下DerivedDerivedClass的内存分布,这里面有三个虚指针了,但base却只有一份。第一张虚表是内含DerivedClass1的,20表示它的虚指针{vbptr}离虚基表指针{vfptr}的距离,第二张虚表是内含DerivedClass2的,12表示它的虚指针{vbptr}离虚基表指针{vfptr}的距离,最后一张表是虚基表,-20指明了它对应的虚指针{vfptr}在内存中的偏移。
虚继承的作用是减少了对基类的重复,代价是增加了虚表指针的负担(更多的虚表指针)。
下面总结一下(当基类有虚函数时):
1. 每个类都有虚指针和虚表;
2. 如果不是虚继承,那么子类将父类的虚指针继承下来,并指向自身的虚表(发生在对象构造时)。有多少个虚函数,虚表里面的项就会有多少。多重继承时,可能存在多个的基类虚表与虚指针;
3. 如果是虚继承,那么子类会有两份虚指针,一份指向自己的虚表,另一份指向虚基表,多重继承时虚基表与虚基表指针有且只有一份。
阅读(...) 评论()C++中的allocator类(内存分配器) - 推酷
C++中的allocator类(内存分配器)
地点:基地二楼
——————————————————————————
& C++的STL中定义了很多容器,容器的第二个模板参数通常为allocator类型,于是想对这一类型做个透彻的了解,看看到底是怎么回事。标准库中allocator类定义在头文件memory中,用于帮助将内存分配和对象的构造分离开来。它分配的内存是原始的、未构造的。和vector等一样,allocator也是一个模板类,为了定义一个allocator对象,我们需指明这个allocator可以分配的对象类型,这样allocator好根据给定的对象类型来确定合适的内存空间大小和对齐位置,例:
allocator&string&
定义了一个可以分配string的allocator对象
auto const p=alloc.allocate(n);
//分配n个未初始化的string内存,即为n个空string分配了内存,当然正如上面所说,分配的内存是原始的,未构造的。
——————————————————————————
二、allocator用法概述
常见操作总结如下:
allocator&T& a & & & 定义了一个名为a的allocator对象,它可以为类型T的对象分配内存
a.allocator(n) & & & 分配一段原始的、未构造的内存,这段内存能保存n个类型为T的对象
a.deallocate(p,n) & &释放T*指针p地址开始的内存,这块内存保存了n个类型为T的对象,p必须是一个先前由allocate返回的指针,且n必须是p创建时所要求的大小,且在调用该函数之前必须销毁在这片内存上创建的对象。要求还蛮多的哈,这是因为在创建过程中我们分配的是最原始的内存,所以在释放内存时也是只能严格释放这片最原始的内存。
a.construct(p,args) &p必须是一个类型为T* 的指针,指向一片原始内存,arg将被传递给类型为T的构造函数,用来在p指向的原始内存上构建对象。
a.destory(p) &p为T*类型的指针,用于对p指向的对象执行析构函数
——————————————————————————
& 1.allocate用于分配原始内存
正如标题所说,allocator出来的内存是最原始的,未构造的内存。相当于开辟新天地,我们将在这片新天地上盖高楼建大厦。它的construct成员函数接受一个指针和零个或多个额外的参数,在给定位置构造对象,额外的参数是用于初始化构造对象的。
//q指向最后构造的元素之后的位置
alloc.construct(q++);
//*q为空字符串
alloc.construct(q++,10,'c');
//*q为cccccccccc
alloc.construct(q++,&hi&);
用完对象后,必须对这种构造的的对象调用destory销毁,它接受一个指针,对指向的对象执行析构函数。
while(q!=p)
alloc.destory(--q);
循环开始处,q是指向最后构造的元素之后的一个位置,调用destory之前我们先对q进行递减操作,所以第一次调用destory销毁的是最后一个元素,依次执行销毁操作直到q和p相等。我们只能对真正构造了的元素进行destory操作。一旦元素被销毁,就可以重新使用这部分内存来保存其他string或归还给系统,释放内存通过调用deallocate完成。
alloc.deallocate(p,n);
其中p不能为空,必须指向allocate分配的内存,而且大小参数n也必须与调用allocated分配内存时提供的大小参数相等。
——————————————————————————
四、两个伴随算法
& allocator还有两个伴随算法,用于在未初始化的内存块中创建对象,这些函数在给定目的位置创建元素,而不是由系统分配内存给他们,同样它们也位于头文件memory中。
uninitialized_copy(b,e,b2) & & & 从迭代器b和e指出的输入范围中拷贝元素到迭代器b2指定的未构造的原始内存中,b2指向的内存必须足够大,能容纳输入序列中元素的拷贝。
uninitialized_copy_n(b,n,b2) & 从迭代器b指向的元素开始,拷贝n个元素到b2开始的内存中
uninitialized_fill(b,e,t) & & & & & & & 在迭代器b和e指定的原始内存范围中创建对象,对象的值均为t的拷贝
uninitalized_fiil_n(b,n,t) & & & & & & 在迭代器b指向的内存地址开始创建n个对象,b必须指向足够大的未构造的原始内存,能够容乃给定数量的对象
以上函数将返回一个迭代器,指向最后一个构造的元素之后的位置。
——————————————————————————
& 假定有一个int的vector,希望将它的内容拷贝到动态内存中,我们将分配一块比vector中元素所占空间大一倍的动态内存,然后将原vector中的元素拷贝到前一半空间,后一半用一个给定值进行填充。
//分配比vi向量所占空间大一倍的动态内存
auto p=alloc.allocate(vi.size()*2);
//通过拷贝vi中的元素来构造从p开始的元素
auto q=uninitialized_copy(vi.begin(),vi.end(),p);
//将剩余元素初始化为42
uninitialized_fill_n(q,vi.size(),42);
——————————————————————————
& 为什么会有allocator?
& 原因是new在内存分配上面有一些局限性,new的机制是将内存分配和对象构造组合在一起,同样的,delete也是将对象析构和内存释放组合在一起。但当分配一块大块内存时,我们想要自己在这块内存上构建对象,就像建房子,我们弄到一块地,想自己开发更赚钱,或更满足自己的需求,这中情况下我们希望将内存分配和对象构造分离,这样就可实现,我们可以事先得到大块内存,然后真正需要时就在这块内存上创建对象。
已发表评论数()
请填写推刊名
描述不能大于100个字符!
权限设置: 公开
仅自己可见
正文不准确
标题不准确
排版有问题
主题不准确
没有分页内容
图片无法显示
视频无法显示
与原文不一致16:01 提问
c++对象内存分配的问题,如何确定在堆上还是在栈上?
c++中对象内存放堆上还是在栈上是不是根据对象实例化的方法,如:
A //在栈中分配内存
A * a = new A(); //动态内存分配,在堆上
要是这样,如果我尽量不用指针的话(这样程序风格似乎更加清晰一些),那是不是对象都分配到栈上了?这有什么弊端吗?栈的空间是不是很有限?是不是为了栈空间的问题我们必须尽量多用new分配内存呢?类成员是不是也要多用指针呢?
按赞数排序
经过昨晚大家的热心帮助,对该问题的疑问已经烟消云散,谢谢大家!在此总结一下:
“非new的对象在栈中分配,new的对象在堆中分配” 这句话是没有问题的。而这个
A(void) :v1(10) { v1[0] = "abc"; }
vector v1;
只是一个类的定义,还没有实例化,是不涉及到任何内存分配的,根本不适用于内存分配原则。就算实例化后,v1变量只是a对象的一个成员变量,它在堆上还是在栈上是由a对象的分配方式决定的,而不是由其声明方式决定的。
用new分配的一个很重要的原因不仅是在堆上分配控件,而是说需要分配多少空间在编写程序的时候不确定。特别是数组,如果要不用动态分配,我们必须在开发的时候估算数组大小的上限,以防不够用。
但是多半这么做是浪费的。
堆栈的确有容量的限制,特别是当你递归调用的时候,对堆栈使用越多,你的递归层数就越少。
但是堆栈的好处是,它会自动分配/清栈,而动态分配你必须时刻小心,及时释放,以免内存泄漏。
我说的是编译时和运行时。
比如说,你的程序是这个逻辑:
请输入数组的大小:
请输入每个元素
显然,这里对于运行时,我们可以知道大小,但是对于编译时,我们就不知道大小。
多年前,我看了32bit windows的内存分配,栈的默认大小是2MB,而堆应该是2GB。
在栈上访问内存理论上来讲效率高一些,但是大规模数据的话,不够用。
堆与栈都有不同的使用情况,对于动态数据,比较大的结构体,类,我们建议使用new,放在堆中,栈中主要还是改局部变量和函数调用栈使用的,栈的大小 是有限的!
动态分配内存时可以使用智能指针来包装,使用RAII原则,基本上可以解决内存泄漏问题。
堆与栈都有不同的使用情况,对于动态数据,比较大的结构体,类,我们建议使用new,放在堆中,栈中主要还是改局部变量和函数调用栈使用的,栈的大小 是有限的!
动态分配内存时可以使用智能指针来包装,使用RAII原则,基本上可以解决内存泄漏问题。
晕,我没有这么说,编译器会判断和决定如何分配内存。你定义的局部变量都在堆栈上,而malloc new的都在堆上,这是固定的。
栈上分配快,但是空间比较小,而且局部变量等数据在栈上,它会自动释放,而堆的生命期受你控制,可以分配的空间大
其他相似问题

我要回帖

更多关于 c语言内存分配方式 的文章

 

随机推荐