如果内存的容量足够,但是,空闲的内存的分布却不连续,那么,此时new是否会失败??

aprccherry 2005-01-05 02:09:44
也就是内存的碎片特别多,但是我new的大小都比一个碎片要大,但是又不是巨大的。此时,new 的调用没问题吧???
...全文
1670 45 打赏 收藏 转发到动态 举报
写回复
用AI写文章
45 条回复
切换为时间正序
请发表友善的回复…
发表回复
zhz3996 2005-02-01
  • 打赏
  • 举报
回复
学习
zhouyu11 2005-02-01
  • 打赏
  • 举报
回复
学习ING
Leaveye 2005-02-01
  • 打赏
  • 举报
回复
我的看法是:

首先,在内存的容量足够,而空闲的内存的分布却不连续的情况下 new 操作是会失败的。
空间不足肯定导致分配失败。至于所有碎片的合集是否大于所需空间不在 new 的考虑范围内。空间不足是需要程序员自己处理的。这个概念总是被隐含式地提到。一个比较典型的例子就是 EC++ 中,对 new 操作实现和对 new-handler 函数功能的要求。该文中对 new 操作的详细描述见后。

其次,对于楼主的命题,认为一个隐含的前提就是所指的内存是虚拟内存空间而不是由操作系统管理的物理内存。
物理内存对于作为客户的应用程序是不可见的,其能访问和操作的是虚拟内存。问题是系统是否会对提供给客户的虚拟内存自动地进行碎片整理。对作为提供者的操作系统来说,操作(如搬移之类,并不是指因为管理内存空间而进行的操作)虚拟内存的具体数据相当于改变程序的运行时状态,这样就会改变程序运行的效果,而这种性质的行为不会是操作系统的功能。从这个意义上讲,操作系统是不会理会客户程序造成了它自己空间的多么混乱的状态的。倒是由于其有能力进行这种维持秩序的碎片整理行为,得到授权的第三方程序(即使作者与系统相同,也只会是一个独立存在的工具)便有可能进行维持秩序的工作。但这不是系统的功能。
值得一提的是 .NET Framework 的 GC(GarbageCollector)机制。它虽然可以自动地进行垃圾回收(暂且不理其时机是否恰到好处,这个问题不是现在的我能够考虑清楚的 :p ),但由于 .NET Framework 在整个软件系统运行中的特殊地位,使其不能被归为系统功能的一部分。整个 .NET Framework 相当于在系统的层次上客户的层次下加入了一层封装,它增强、完善、约束了许多原本系统中遍地开花的各种功能,使得作为最终客户的用户程序能够“安全”、“稳定”、“高效”地运行在系统中。(各种不确定性使我不得不加这几个引号,sigh..)这种性质也使 .NET Framework 与用户程序成为一个整体,作为系统的客户而存在。
因此,在内存的容量足够,而空闲的内存的分布却不连续的情况下对于系统的 new 操作是会失败的。


欢迎指正。



关于 Effective C++ 中对 new 操作的相关说明。

条款 8 :operator new实际上会不只一次地尝试着去分配内存,它要在每次失败后调用出错处理函数,还期望出错处理函数能想办法释放别处的内存。只有在指向出错处理函数的指针为空的情况下,operator new才抛出异常。
非类成员形式的operator new的伪代码看起来会象下面这样:
void * operator new(size_t size) // operator new还可能有其它参数
{

if (size == 0) { // 处理0字节请求时,
size = 1; // 把它当作1个字节请求来处理
}
while (1) {
分配size字节内存;

if (分配成功)
return (指向内存的指针);

// 分配不成功,找出当前出错处理函数
new_handler globalhandler = set_new_handler(0);
set_new_handler(globalhandler);

if (globalhandler) (*globalhandler)();
else throw std::bad_alloc();
}
}

条款 7 :一个设计得好的new-handler函数必须实现下面功能中的一种。
·产生更多的可用内存。这将使operator new下一次分配内存的尝试有可能获得成功。
·安装另一个可以提供更多的资源的new-handler函数。
·卸除new-handler。也就是传递空指针给set_new_handler。没有安装new-handler,operator new分配内存不成功时就会抛出一个标准的std::bad_alloc类型的异常。
·抛出std::bad_alloc或从std::bad_alloc继承的其他类型的异常。
·没有返回。典型做法是调用abort或exit。abort/exit可以在标准c库中找到(还有标准c++库,参见条款49)。

xiaohaiyan 2005-02-01
  • 打赏
  • 举报
回复
呵呵,正如steedhorse(晨星) 同学所讲,这个东东是操作系统层面的问题,自然与各个操作系统的实现有关。
对于没有实现虚拟内存分配的平台而言,失败是一定的。
对于实现虚拟内存分配的平台,成功也不是一定的。

不知道能不能算个结论,呵呵。。
xiaohaiyan 2005-02-01
  • 打赏
  • 举报
回复
1.vxworks 系统长时间运行之后中,show buffer 一看还有两兆多,但是malloc 一块1.5兆的内存,失败。当然这里指的都是物理内存。(vxworks的内存管理一只为人所诟病,因为释放内存的时候不作内存合并)

2.Linux 中的kmalloc 运用的是slab 机制。。,kmalloc后台内存分配程序是否实现虚拟内存分配,应该是吧。。否则虚拟内存的有点何从谈起。呵呵


3.大家常用的都是window平台。具体可以参考http://www.microsoft.com/china/msdn/archives/technic/develop/win32ram.asp
http://www.microsoft.com/china/msdn/archives/library/techart/heap3.asp
VirtualAlloc ,VirtualFree 通过内存的映射,可以将连续的虚拟地址 映射到 非连续的物理地址。
至于C/C++ 运行时 (CRT) 分配程序:如malloc() 和 free() 以及 new 和 delete 操作符,返回的只是虚拟内存地址而已,因为最终管理内存分配的是虚拟内存分配程序,所以也应该能够做到内存的映射(fix me )

4: unix 平台,莱昂氏上的malloc 只是通过链表简单的管理sbrk分配的物理内存,所以分配一定会使失败。其他unix平台不熟悉。

至于各种嵌入式平台,失败是一定的,虚拟内存管理cost太大。



  • 打赏
  • 举报
回复
如果楼主指的内存是逻辑内存,即用户地址空间,而不是物理内存的话;而且你的程序有不是使用支持自动垃圾回收的语言写的,那么new回失败。
晨星 2005-01-31
  • 打赏
  • 举报
回复
“我觉得linux关于new函数的源码中,也许会有这个问题的答案。”
——“new”是“linux的函数”吗?new连个函数都不是,是个操作符。
而且linux也不是C++写的,而是C写的。

编译器会把分配内存的函数或语句编译成响应的系统调用。

而即使你找到Linux平台上分配内存的系统调用的源代码,那也只能说明Linux是那样做的,而Windows就未必是那样做的。

你完全可以研究这个问题,而且这也是个很好的,很值得研究的问题。
但问题是你必须明白这是个操作系统设计上的问题,不是应用程序设计上的问题,更不是应用程序设计者在开发应用程序时有必要考虑的问题。
aprccherry 2005-01-31
  • 打赏
  • 举报
回复
我的问题引来诸位高手的热烈讨论,小弟不胜感激。

虽然,对于这个问题还没有一个足够权威和足够信服的答案。但是,我还是希望某位高人能够给予最后的判定。

我觉得linux关于new函数的源码中,也许会有这个问题的答案。
carbonic 2005-01-31
  • 打赏
  • 举报
回复
以前学过java ,不会的
flyyourdream 2005-01-21
  • 打赏
  • 举报
回复
又查了《操作系统》,感觉还是不好解决这个问题,头大了!我觉得不管“页式管理”还是“段式管理”还是“虚拟内存”都是针对程序代码,也就是针对一系列0,1码如何在内存中存放的问题,而我们现在讨论的问题是在执行这些0,1序列的过程中又提出数据存储请求,系统该如何分配,好象数据存储在堆里吧,我不是很清楚,虽然堆也是内存的一部分空间,但是具体怎么分配,是否存在“碎片”的问题,是否有特殊的分配策略,我真的不知道了,而且找不到具体的资料明确的回答这个问题,糊涂ing......郁闷啊!!!!!!!!!!!!!
flyyourdream 2005-01-20
  • 打赏
  • 举报
回复
我也认为new应该分配连续内存。
Zark 2005-01-19
  • 打赏
  • 举报
回复
现在讨论的命题是"如果内存的容量足够,但是,空闲的内存的分布却不连续,那么,此时new是否会失败? ", 这与我可不可以中彩票似乎是风马牛不相及, 如果的确需要讨论,那么可以在"灌水区"另开一贴,不必在此"聒噪".

对此命题,我的结论是"new是会失败的",如果"海魂"先生持相反意见,那么就应该是"new是一定会成功的",因此你应该是可以说服大家,即使在我所举的"极端"的条件下,你的结论也是正确可靠的.

为了便于你的讨论,这里将那个"极端"的情况扩展一下,使之可以吻合于你的"页式管理". 假定系统可用的"页面"为X个,每个"页面"大小为Y (K bytes),并假定系统中只有一个"程序"在远行,这个"程序"连续使用X次 new char [Y*1024/2+1] (目的是耗尽你的页面),然后再做一次new char [Y*1024/2+1],请问是不是一定会成功呢?
sandrowjw 2005-01-19
  • 打赏
  • 举报
回复
new是不会分配不连续内存的,也不可能在页面内部移动内存块来整理内存。
new可能会合并一些邻近的内存(虽然在空闲内存列表中它们是不连续的),如果没有任何空闲的内存,则唯一可行的手段是进行换页。如果没有页框可以换出,那么就会失败(这里我觉得还应该考虑有一些页框可能不允许被换出的情况)。
jj_k 2005-01-18
  • 打赏
  • 举报
回复
我认为new出来的是连续的地址,
如果没有如此大的连续空间会失败,
通常堆是有大小限制的吧,
同时程序运行的时候空间和地址已经确定了,

继续研究。。。
ddring 2005-01-18
  • 打赏
  • 举报
回复
失败是肯定的,因为我的程序就遇到过,一段连续的大内存NEW失败,但是几段大小加起来超过它的小内存却能NEW成功,不知道WINDOWS的内存管理机制是什么原理,它号称是可以回收和合并DELETE掉的内存的啊
aprccherry 2005-01-18
  • 打赏
  • 举报
回复
Zark(金陵五月)说的有道理,在new char4G次,然后释放2G偶数char,如果再new char[2]会失败。但是有一个问题:
new 函数应该不是那么死心眼,就会申请连续内存。我觉得new应该满足一定大小的范围内,他是new出连续的地址,如果new得大小超过一定的大小范围,那么new出来的地址应该是不连续的。并且会有一个类似于叶面管理之类的机制,把这些颗粒给连起来。
kevinmartin 2005-01-18
  • 打赏
  • 举报
回复
操作系统是无法进行所谓的内存整理的,你可以设想一下,假如:

char* ptr=new char[100];
...
...
char* pIAmHidden=ptr;

..... (如果在这个时候"整理"内存,那么ptr的值将会改变,OS有能力知道ptr这个变量的地址吗?有能力知道pIAmHidden也必须随之改变吗?)

.....
*ptr='1';
....
*pIAmHidden='1';
...

所以我认为楼主问题的答案是: 此时new是会失败的.

再举一个极端的例子,大家都知道虚内存是4GB,当然系统要占去一些,假设不考虑它们,我们假定4GB都可用,那么如果我们使用new char; 4G次,然后把偶数次的new都释放掉,这样就形成了解xoxoxoxo这样的内存分布,这个时候我们再用new char[2],是不是会失败呢?大家可以试验一下,这个例子是为了说明"页面分配"对楼主的问题没有帮助的.
-----------------------------------------------------------------------

我看你是根本不懂“页式管理”的概念,你好好去看一下吧。每一个应用程序可以使用高达4G的、在应用程序级别开来是完全连续的内存空间。什么意思?举个例子给你看:
假如物理内存256M,操作系统将其分为10000个页面,有30个程序在运行,假如这些程序每一个都号称能够访问4G内存空间,程序01使用了1,2,3,4,5页面,02使用了6,7,8页面,03使用了9,10,11,12,13页面。然后02结束运行,6,7,8页面被释放,04开始运行,需要10个页面,他就会用6,7,8,14,15,16,17,18,19,20。这里物理内存并不连续,但是操作系统处理完成以后,在应用程序级别上看来,04一共使用了256K的内存,他也不知道什么页面的概念,他只知道我这个应用程序从0a00开始,一共使用了后面连续的256K的内存。

按照这种情况看来,只要操作系统的所有内存没有被消耗干净,也就是这1000个页面中只要还有一个可以用,内存分配就可以成功,只要所有的页面的内存加起来,总和大于申请的内存数量,那么申请就会成功。不成功只有一种情况,那就是所有的页面都没有了。当然,虽然支持4G的虚拟内存,但是实际内存使用和你机器的物理内存+虚拟内存(硬盘上的)总和有关。如果你是32M内存+64M虚拟内存,那肯定在运行程序的时候经常出现错误,是内存不够。但是只要内存总数还够,不会出现所谓的内存分配失败的问题。

另外一个问题:按照页式管理的方法,操作系统只要整理页面的顺序,可以达到整理内存的目的,比如说内存分成了100快,01使用了1,3,5,8,9,10,15。02使用了4,7,12,58,92。那么操作系统只要将自己的内存映射修改一下,将本来内存快3的内容移到2内,并且将映射修改到2,其余的同样操作,就可以让一个程序所使用的物理内存变成连续的。结果就是01使用了1,2,3,4,5,6,7。02使用了8,9,10,11,12。

-------------------------------------------------------
Zark(金陵五月)说的有道理,在new char4G次,然后释放2G偶数char,如果再new char[2]会失败。但是有一个问题:
new 函数应该不是那么死心眼,就会申请连续内存。我觉得new应该满足一定大小的范围内,他是new出连续的地址,如果new得大小超过一定的大小范围,那么new出来的地址应该是不连续的。并且会有一个类似于叶面管理之类的机制,把这些颗粒给连起来。
----------------------------------------------------------
我们不是说这种情况肯定不会发生,只是概率非常小,楼上说的这种情况更是精品中的精品了。当然,谁要是碰到这种情况,赶快去买彩票,我敢打赌,出现这种情况的概率比同时获得足彩、福彩、等等所有彩票的所有奖项的概率都要低。赶快去买彩票,买多少种多少特等奖!
期待这种人的出现!
prgmBaggio 2005-01-18
  • 打赏
  • 举报
回复
学习学习啊
ddddh 2005-01-18
  • 打赏
  • 举报
回复
抱歉:

于是找出空间的物理内存 -〉于是找出空**闲**的物理内存
ddddh 2005-01-18
  • 打赏
  • 举报
回复
呵呵,上面很多人混淆了“物理内存”和“虚拟内存”的概念。

运行在windows的应用,是看不到物理内存的,他能看到的只是虚拟空间的地址,cpu在os的配合下,把这个地址,通过一些较为繁琐的手段,映射为物理地址,然后cpu从物理地址中读取数据。再提醒,应用程序看不到上面的过程,它只是要求读取一个虚拟地址,然后返回过来一个值,中间的过程它是不知道的。

这样os就拥有权力,可以把虚拟地址对应的什么物理地址,来换掉。比方说,char *p = new char[100]; p指向的虚拟地址,比方说为0x1E3c58,原来是映射到物理内存0x10000的,os把内存0x10000处的一个页面(4k)保存到磁盘(所谓的交换文件)然后断开0x1E3c58(虚拟地址)到0x10000(物理地址)的连接。

当application要访问p,也即0x1E3c58的时候,os察觉到它对应的地址在硬盘上,于是找出空间的物理内存,比方说0x30000,把交换文件中原来的值再给读到0x30000去,同时把0x1E3c58(虚拟地址)和0x30000(物理地址)建立连接(通过页目录,页表等等较为复杂的机制,详见讲保护模式原理的书)。此后对p(0x1e3c58)的访问,都会被定位到0x30000(物理地址)上。os仍然可以继续上面的步骤,把p映射到不同的物理内存上。


从这里可以得到结论:
1. 物理内存是否连续是无关紧要的
2. 如果虚拟地址空间碎片化,os是无能为力的。因此楼主说的情况是存在的,虽然概率很小。


随便写的,估计很多错误,希望高人指正。同时希望对楼主有帮助。
加载更多回复(25)

69,371

社区成员

发帖
与我相关
我的任务
社区描述
C语言相关问题讨论
社区管理员
  • C语言
  • 花神庙码农
  • 架构师李肯
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧