您的位置:首页 > 编程语言 > C语言/C++

动态存储分配(Thinking in C++)

2006-11-13 16:34 302 查看
        你可能从来都不知道你会需要最多多少的栈存储空间,这样内存就会从heap(动态目标内存池)中分配出来一部分,heap是一大块内存在运行的时候被用来分配成一小片一小片,当你写程序的时候并且不知道内存有多大的时候你会需要去用heap,也就是说,只有当在程序运行的时候你才会发现你需要用200架飞机变量大小的内存支持代替20架飞机的大小位置。在标准C中,动态存储分配的函数包括malloc(),calloc(),realloc(),free()。替代了库调用,但是,C++有更多更好的动态存储方法综合在了语言中,通过关键字new和delete。

Inflate()函数用new关键字获得了更大块的栈空间,在这样的情况下,我们只会耗费内存而不会让可用内存增加,并且assert()会保证那些错误的数字不会被inflate()当作增量接收。新的数字元素可以被规划成拥有新的数量的内存,并且会根据每一个数字所占字节的大小增加到那些被分配到的地方。这样我们就知道有多少字节需要在原来的分配基础上增加多少了(原来的字节是通过原来的数量计算出来的)。

实际的内存分配发生在那些包括关键子new的表达式中:
New unsigned char[newBytes];
从这个表达式可以概括出来的的有:
新的类型(New Type);
我们希望在heap中分配并描述变量类型,在上述情况下,我们想得到一个unsigned char型的数组,总共有newbytes的长度,这就是我们看到的类型,我们同样可以分配一些类似这样的int型,例如:
New int;
同样的做法,我们可以从中看到一致的地方。

一个表达式返回一个你所需要的准确的对象类型的指针,所以当你使用了new Type,你会得到一个Type类型的指针,如果你使用了new int,你会得到一个int型的指针,如果我们想得到一个无符号类型的字符型数组(new unsigned char),你会得到当前数组的第一个元素的指针。编译器会确保你在表达式中分配当前类型的指针的返回值的正确性。

当然,任何时间你申请内存都有可能出现失败的情况(如果没有足够的内存)。就象你曾经学过的那些,C++也有机械论者在分配内存操作失败的情况下这样做。

一旦新的存储空间被分配了,旧数据就必须被拷贝到新的存储空间中;这又需要通过数组索引来完成,在一个循环中复制一个字符。在字符复制完成后,旧的存储空间必须被释放,这样当别的程序需要存储空间的时候,旧的存储空间就可以被程序的其他地方使用。delete关键字就用来解决new的问题,并且只能释放那些由new关键字申请的内存(如果你忘记使用delete去释放内存,内存就会保持不可用状态,当这样被成为内存泄露的情况发生的足够多的时候,就会出现内存溢出)。另外,还有一个特殊的语法当你需要删除一个数组的时候,就好象你必须提醒编译器这样的指针不仅仅指向一个对象,而是一个数组对象:
使用一个空的放括号在需要删除的指针的前面:
Delete []myArray;

一旦旧的存储空间被删除了,指向新的存储空间的指针就能被分配到新的存储空间(数量也被重新调整)并完成了它的使命。

请注意heap管理是真正的开始。他给了你大块的内存,当你使用了delete的时候它又能将这些内存收回,并没有固有的工具和设备提供给heap更大的并且空闲的内存块的压缩。如果程序分配并且释放heap存储空间了一段时间,你在结束程序的时候可以拥有很多的heap片(内存片),但是不能在那个时候将这些heap片再分配个任何一个比当前片大的空间。
一个heap碎片机器让程序变的更复杂是因为它到处移动内存块,所以你的指针不会保持他们原有的值。一些操作环境让heap处于压缩状态,但是它们要求你去使用特殊的内存句柄(临时修改指针,在锁定了内存后heap碎片机就可以移动他们)来代替指针。你同样可以生成属于你自己的heap碎片机配置方案,但这不是个多么轻松就可以承担的任务。

 当你在编译的时候在栈中创建了一个变量的时候,通过编译器,存储那个变量的内存会自动的创建和释放。编译器会准确的知道需要多大的空间去存储,而且根据他的范围来确定变量的生命时间。虽然使用动态内存分配,但是编译器并不知道你将会需要多少的存储空间,而且也不知道将会需要多少时间去维持那个存储空间。因为存储空间不会自动去清理,这样的话,你就有责任去释放这些存储空间通过使用delete来告诉heap管理空间去让下一个new的地方能使用这些内存空间。在库中合理的位置使用cleanup()函数来控制这样的发生以为可以释放所有该释放的内存单元。

重要提示:
对于每一个编译单元,编译器会创建一个目标文件目录,包裹以.o和.obj结尾的文件范围之内的其他相似文件,这些目标文件(伴随着必须的启动代码)必须连接器收集并变成可执行程序,在连接的过程中,所有外部涉及到的内容必须重新储存。举例来说,在ClibTest.cpp中,象initialize()或者fetch()这样的函数被声名(编译器告诉我们他们看起来象是什么作用)和使用,但并没有定义,他们在其他地方被定义(在Clib.cpp中)。这样的话,在Clip.cpp中的调用是外部涉及到的内容。连接器必须(当它们将所有的目标文件整理到一起的时候)找到所有的未储存的外部涉及到的函数,并找到他们的地址(他们被定义的地方)。这些地址被放入到那些可执行程序并代替外部涉及到的函数调用的地方。在C语言中这些都是很有必要去认识的,外部调用的时候连接器知识找到了函数的名字,就象我们给他们标注了下划线一样,这样所有的连接器在目标文件中都会去将函数的名字去和函数体匹配(在函数调用的地方),如果你偶然使用了一个被解释成fun(int)的函数,而函数体在其他目标文件中却是fun(float)型,并且可以被通过,fun()在被调用的地方会将一个int型入栈,如果函数只是读取栈中的数据而没有任何写入的操作,栈的大小是不会变的,事实上,float型的值当被从栈中读出来的时候会可能产生多种理解,这就是我们很难找到bug的原因了。 
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息