返回   cpper编程论坛 > 技术杂烩
注册账号 论坛帮助 会员列表 日历事件 搜索 今日新帖 标记版面已读

技术杂烩 找不到地方的技术问题?这里!

回复
 
LinkBack 主题工具 显示模式
  #81 (permalink)  
旧 2008-03-06
sjinny 的头像
普通会员
 
注册日期: 2008-02-01
帖子: 66
sjinny 正向着好的方向发展
默认 回复: 请教各位大大关于动态内存管理的问题……

RVO感觉有点像为了性能优化而破坏了一些约束。

关于GC:
首先这个“优势”是gc相对于谁而言的什么方面的优势。
在我看来,唯一可能有优势的方面,就是程序员对于内存的管理可能可以少花一点精力。但是,我只能得到一个对象被启用的时机(构造函数),但是我却得不到一个对象被弃用的时机,而其他技术和gc的很大的一个区别就是能够得到弃用时机(析构函数)。如果gc里的析构函数是可用的,那么得到的也只是对象被销毁的时机,而不是“不再被需要的时机”。
所以,即使不考虑优化时要理解内存的使用和释放会因为gc的“自主性”而更困难,gc首先就因为延后释放而不适宜用于管理其它资源。
如果gc不能提供一个通用的资源管理能力,那么只会使资源管理复杂化。
我觉得OO的一个特点就是把数据和行为绑定在一起,并且把构造函数和析构函数作为访问启用时机和弃用时机的途径。而gc却要把数据的管理和行为管理分开来(毕竟关闭文件之类的资源管理主要就是一些特殊的行为),我想这就是问题所在。
Digg this Post!Add Post to del.icio.usBookmark Post in TechnoratiFurl this Post!
回复时引用此帖
  #82 (permalink)  
旧 2008-03-06
sjinny 的头像
普通会员
 
注册日期: 2008-02-01
帖子: 66
sjinny 正向着好的方向发展
默认 回复: 请教各位大大关于动态内存管理的问题……

现在感觉思绪有些混乱。
那个花括号的问题,我主要的目的是想说明:
虽然自动化分析能够分析出一些东西,但是有些信息,如果程序员没有表达出来,那么自动分析是不可能分析出这些信息的。
代码:
{ Integer i = new Integer(1); Integer j = i; System.out.println(i); }
这段代码里,如果一开始i和j就是设计为内部使用的临时变量,那么如果这么写,gc可能也能分析出这一点然后放到栈上。但是就这段代码本身而言,无论是gc还是阅读它的人,都无法搞清楚:这i以后能不能传递到外面去?
现在的事实是,这i没有传到外面,但是原始设计中,这i是否允许传到外面去呢?如果设计不允许,那么最多就只能用拷贝的方法;而如果允许,那么就可以传递引用。但是这段代码里没有描述出这些信息。如果用栈来管理,变成:
代码:
{ Integer i(1); Integer& j = i; System.out.println(i); }
那么这时一眼就能看出来:i是个内部变量,它不应该被(直接按引用)传递到外面。

我又想起一个例子,比如说我经常会使用这样的一段控制代码:
代码:
int main( int argc, char* argv[] ) { bool running= true; State state; while( running ){ switch( state ){ case INIT: ... break; case PROC: ... break; case FINI: ... break; } } }
这里的running是在while循环之外的,但是它实际上只是在while循环中起一个控制作用,在while循环外是没有意义的,所以这段代码里,running的事实上的生存期与它应该的生存期是不一致的。所以我现在改成这样:
代码:
for( bool running= true; running; ){ switch(){ ... } }
这时如果在for循环之外使用running,那么编译会报错。
这段代码因为我用成了习惯,所以不容易把running的作用理解错,可是如果别人不习惯,那么第二种方式既可以描述更强的约束(提供了更多的信息),又可以依靠编译器来实现这种约束。
总之,我希望能尽量把设计信息融入代码,使得代码不仅现在能运作良好,而且能为以后的维护和复用提供更多的信息。
Digg this Post!Add Post to del.icio.usBookmark Post in TechnoratiFurl this Post!
回复时引用此帖
  #83 (permalink)  
旧 2008-03-06
cat cat 当前离线
高级会员
 
注册日期: 2003-11-06
帖子: 1,560
文章: 6
cat 正向着好的方向发展
默认 回复: 请教各位大大关于动态内存管理的问题……

引用:
作者: sjinny
但是,我只能得到一个对象被启用的时机(构造函数),但是我却得不到一个对象被弃用的时机,而其他技术和gc的很大的一个区别就是能够得到弃用时机(析构函数)。如果gc里的析构函数是可用的,那么得到的也只是对象被销毁的时机,而不是“不再被需要的时机”。
不是所有对象你都希望知道它的销毁时机的,对一你希望知道的,就用using/Dispose就是了。而dtor/delete显示管理就强制你必须知道销毁时机,实际上减少了你的选择。

引用:
作者: sjinny
gc首先就因为延后释放而不适宜用于管理其它资源。如果gc不能提供一个通用的资源管理能力,那么只会使资源管理复杂化。
gc也没说普遍适用于所有类型的资源管理啊。唯有如此才能做针对性的优化。资源太广泛了。内存管理就是内存管理,和打印机管理,硬盘管理,文件管理有一定的联系但各有特点。
就说内存,现代的gc也很复杂,对象分代,大对象自己有存储区域,并不是一个“优雅而通用”,写出来才几行的算法。

引用:
作者: sjinny
这时如果在for循环之外使用running,那么编译会报错。
over engineering. 不要觉得别人都看不懂code. 不然就一个人写不出这么多程序的。

此帖于 2008-03-06 08:16 PM 被 cat 编辑.
Digg this Post!Add Post to del.icio.usBookmark Post in TechnoratiFurl this Post!
回复时引用此帖
  #84 (permalink)  
旧 2008-03-06
sjinny 的头像
普通会员
 
注册日期: 2008-02-01
帖子: 66
sjinny 正向着好的方向发展
默认 回复: 请教各位大大关于动态内存管理的问题……

查了一下,似乎using/Dispose和栈的区别不大——除了语法上没有栈简洁之外……
而栈的确是我心目中最最优先使用的资源管理技术。
但是,当对象分配在栈上时,构造函数和析构函数仍然是有效的,实际上这是很重要的特点:
用花括号对/栈来说明一个生存期 + 栈上对象的构造函数和析构函数被自动绑定在所在的生存期边界上


1.资源管理是很重要的
2.资源管理是很麻烦的
3.gc机制只能用于管理内存,并且使用它也是有成本的(特别是学习它和理解它的成本)
4.栈、内存池、智能指针都可以把非内存资源绑定上去进行管理
1+2+3+4 ==> 所以我把gc放到候选列表的末尾。

资源的确太广泛了,但正因为如此,我们才需要一个通用的技术。否则内存一个管理技术,文件又是另一个技术……如果没有一个通用技术作为基础,那么最后各种不同的管理机制之间的配合和交互就会成为大问题。
我所期望的资源管理是:
一个良好的内存管理机制+一套把任意行为绑定到内存管理机制上的机制+N套管理各种不同资源的技术
中间那个绑定机制,其实就是C++的构造函数/析构函数。后面那些管理技术是真对于各种资源的,但是它们都应该能够绑定到内存管理上,用内存的生命期来指代各种资源的生命期,这样生命期管理就只需要在内存管理那里实现一次。
现在主要的问题在于最前面那个内存管理机制。栈这样的机制,虽然很好,但是表达力有限;内存池的表达力稍微提高了一些,但是其实主要是比栈多了根据runtime需要而分配的内存的管理能力,生命期模型与栈似乎没多少区别。智能指针存在循环引用问题以及性能问题。所以这些机制大多在表达力上有限,但是虽然它们的表达力有限,但是在其擅长的应用领域里还是用得很好的,所以没有理由丢弃它们。
gc的表达力是最强的,但是如果不考虑性能问题,那么还有一个问题,那就是其不确定性使得把其他资源的管理行为绑定上去后的效果并不好,具体就是在gc环境里使用RAII时的问题。

另外,通用优雅并不是指这个技术需要几行代码,而是说它对外的表现和行为的应用范围和可理解性。
其实,一些os中,很多资源都是以文件描述符的形式出现的,通信就是read/write。这是“通用”的例子。至于即通用又优雅的东西,我一时也想不起来。不过这种追求应该是能取得共识的。

不是我觉得别人看不懂,而是我希望减少别人的负担。如果我把这些控制变量写在实际有意义的范围之外,那么别人就需要花费更多的时间精力来猜测一开始的意图,其实一段时间后可能我自己都不得不进行这样的猜测。如果控制变量多了,代码长了,那么在庞大的代码中到处翻箱倒柜地查看就是很麻烦的事情。一开始的C里,函数的变量必须在函数的前头声明,后来到了C++,连for语句里都可以声明变量了。当实现了一个约束时,一方面是说明这个约束一定会成立,是可以依赖的,另一方面也是说明相应的互斥情况一定不会出现,是可靠的。所以C++里的const是为了提高对设计的表达能力,变量声明的控制以及花括号的作用也是如此。把设计信息以一种良好的方式融入代码,会比写进文档更好。
Digg this Post!Add Post to del.icio.usBookmark Post in TechnoratiFurl this Post!
回复时引用此帖
  #85 (permalink)  
旧 2008-03-07
高级会员
 
注册日期: 2002-09-15
帖子: 2,531
ajoo 正向着好的方向发展
默认 回复: 请教各位大大关于动态内存管理的问题……

引用:
作者: sjinny 查看帖子
现在感觉思绪有些混乱。
那个花括号的问题,我主要的目的是想说明:
虽然自动化分析能够分析出一些东西,但是有些信息,如果程序员没有表达出来,那么自动分析是不可能分析出这些信息的。
代码:
{ Integer i = new Integer(1); Integer j = i; System.out.println(i); }
这段代码里,如果一开始i和j就是设计为内部使用的临时变量,那么如果这么写,gc可能也能分析出这一点然后放到栈上。但是就这段代码本身而言,无论是gc还是阅读它的人,都无法搞清楚:这i以后能不能传递到外面去?
现在的事实是,这i没有传到外面,但是原始设计中,这i是否允许传到外面去呢?如果设计不允许,那么最多就只能用拷贝的方法;而如果允许,那么就可以传递引用。但是这段代码里没有描述出这些信息。如果用栈来管理,变成:
代码:
{ Integer i(1); Integer& j = i; System.out.println(i); }
那么这时一眼就能看出来:i是个内部变量,它不应该被(直接按引用)传递到外面。
应该不应该传递到外面是由需求说了算的。没有什么“允许”不“允许”。用gc的话,都是传引用,需要你就弄到外面去,不需要就不弄,多简单的事,搞那么复杂干吗?

引用:
作者: sjinny;31721
我又想起一个例子,比如说我经常会使用这样的一段控制代码:
[code
int main( int argc, char* argv[] )
{
bool running= true;
State state;
while( running ){
switch( state ){
case INIT:
...
break;
case PROC:
...
break;
case FINI:
...
break;
}
}
}
[/code]
这里的running是在while循环之外的,但是它实际上只是在while循环中起一个控制作用,在while循环外是没有意义的,所以这段代码里,running的事实上的生存期与它应该的生存期是不一致的。所以我现在改成这样:
代码:
for( bool running= true; running; ){ switch(){ ... } }
这时如果在for循环之外使用running,那么编译会报错。
这段代码因为我用成了习惯,所以不容易把running的作用理解错,可是如果别人不习惯,那么第二种方式既可以描述更强的约束(提供了更多的信息),又可以依靠编译器来实现这种约束。
总之,我希望能尽量把设计信息融入代码,使得代码不仅现在能运作良好,而且能为以后的维护和复用提供更多的信息。
你是思维有点乱。这个例子明显是一个lexical scope的例子,跟gc有嘛关系?是用gc妨碍了lexical scope还是你不用gc就能防止你主动把running象下面的代码一样泄露到外面去了?
代码:
bool* p; for (bool running = true; ...) { p = &running; }
Digg this Post!Add Post to del.icio.usBookmark Post in TechnoratiFurl this Post!
回复时引用此帖
  #86 (permalink)  
旧 2008-03-07
高级会员
 
注册日期: 2002-09-15
帖子: 2,531
ajoo 正向着好的方向发展
默认 回复: 请教各位大大关于动态内存管理的问题……

引用:
作者: sjinny 查看帖子
4.栈、内存池、智能指针都可以把非内存资源绑定上去进行管理
1+2+3+4 ==> 所以我把gc放到候选列表的末尾。

资源的确太广泛了,但正因为如此,我们才需要一个通用的技术。否则内存一个管理技术,文件又是另一个技术……如果没有一个通用技术作为基础,那么最后各种不同的管理机制之间的配合和交互就会成为大问题。
我所期望的资源管理是:
一个良好的内存管理机制+一套把任意行为绑定到内存管理机制上的机制+N套管理各种不同资源的技术
中间那个绑定机制,其实就是C++的构造函数/析构函数。后面那些管理技术是真对于各种资源的,但是它们都应该能够绑定到内存管理上,用内存的生命期来指代各种资源的生命期,这样生命期管理就只需要在内存管理那里实现一次。
注意,都注意啦!这就是我一贯反对c++的析构和拷贝构造的原因。内存管理就是内存管理,它有它自己的特点(比如可以延期释放;因为无可观察的副作用,可以由编译器进行大幅度的优化,比如放到栈上,rvo,或者重复使用已分配的内存等等),强行把它绑到其它资源的管理上去属于缘木求鱼,自废武功那种。c++设计的retarded-ness由此可见。

其实,用了gc,内存管理基本上已经不是一个主要问题了。你就可以把精力放在管理其它资源上,而让人惊讶的是,相比于繁琐脆弱的内存管理,其它资源的管理工作相当清闲(随便一个狗屁模式都要涉及内存处理,而谁没事总去鼓捣文件,数据库啥的?)。在using或者try-finally(或者我推荐的auto)的帮助下,也非常容易。因为内存这个最关键的资源得到了保障,其它的资源你弄错了最多得到一个ClosedResourceException,而不是神秘不可琢磨的core dump,甚至undefined behavior(比如我明天忽然惊喜地发现银行的一个bug诡异地让我得到100万)。

此帖于 2008-03-07 01:10 AM 被 ajoo 编辑.
Digg this Post!Add Post to del.icio.usBookmark Post in TechnoratiFurl this Post!
回复时引用此帖
  #87 (permalink)  
旧 2008-03-07
Elminster 的头像
超级版主
 
注册日期: 2002-09-09
帖子: 1,763
Elminster 正向着好的方向发展
默认 回复: 请教各位大大关于动态内存管理的问题……

引用:
作者: ajoo 查看帖子
注意,都注意啦!这就是我一贯反对c++的析构和拷贝构造的原因。内存管理就是内存管理,它有它自己的特点(比如可以延期释放;因为无可观察的副作用,可以由编译器进行大幅度的优化,比如放到栈上,rvo,或者重复使用已分配的内存等等),强行把它绑到其它资源的管理上去属于缘木求鱼,自废武功那种。c++设计的retarded-ness由此可见。

其实,用了gc,内存管理基本上已经不是一个主要问题了。你就可以把精力放在管理其它资源上,而让人惊讶的是,相比于繁琐脆弱的内存管理,其它资源的管理工作相当清闲(随便一个狗屁模式都要涉及内存处理,而谁没事总去鼓捣文件,数据库啥的?)。在using或者try-finally(或者我推荐的auto)的帮助下,也非常容易。因为内存这个最关键的资源得到了保障,其它的资源你弄错了最多得到一个ClosedResourceException,而不是神秘不可琢磨的core dump,甚至undefined behavior(比如我明天忽然惊喜地发现银行的一个bug诡异地让我得到100万)。
这就是我和你一贯分歧的地方了:析构和拷贝构造完全可以和内存管理脱钩的,对象的生命期不等于对象所占据的内存块的生命期。用 GC,也可以用析构和拷贝构造。
Digg this Post!Add Post to del.icio.usBookmark Post in TechnoratiFurl this Post!
回复时引用此帖
  #88 (permalink)  
旧 2008-03-07
高级会员
 
注册日期: 2002-09-15
帖子: 2,531
ajoo 正向着好的方向发展
默认 回复: 请教各位大大关于动态内存管理的问题……

引用:
作者: Elminster 查看帖子
这就是我和你一贯分歧的地方了:析构和拷贝构造完全可以和内存管理脱钩的,对象的生命期不等于对象所占据的内存块的生命期。用 GC,也可以用析构和拷贝构造。
关键就在于“对象生命期”是怎么定义的。在c++这种有值语义的语言中,局部栈对象的生命期是什么?是不是所谓的花括号结束处?还是可以在特殊条件下,优化开关打开的情况下,允许对象进行超光速空间跳跃?
Digg this Post!Add Post to del.icio.usBookmark Post in TechnoratiFurl this Post!
回复时引用此帖
  #89 (permalink)  
旧 2008-03-07
sjinny 的头像
普通会员
 
注册日期: 2008-02-01
帖子: 66
sjinny 正向着好的方向发展
默认 回复: 请教各位大大关于动态内存管理的问题……

花括号的第一个作用是表达作用,即使会依赖于程序员之间的约定,但是这仍然能够用来表达一些信息,这是为了以后的理解和维护而着想的。以后的需求会变,但是在改动代码之前,至少应该知道这段代码以前的设计情况。
你那段代码的确可以把running泄漏到外面去,但是如果是一开始写代码的人这么做的,那么只能说这是有意而为之,这是初始设计者所设计的;如果是维护者的修改,那么至少代码本身能够表达这样的信息:维护者把一个在最初设计中的局部变量泄露到了最初设计的作用域之外。而如果代码没有那么写,那么维护者根据什么来了解最初的设计?如果没有了解到最初的设计,那么如何判断修改的风险?
这就像reinterpret_cast,虽然编译器不能阻止程序员做危险的转换,但是能够通过“丑陋”的语法来提示程序员其中的风险。把running放进一个更精确的作用域里声明,虽然无法真正阻止一些事情(如果程序员有意乱搞,神仙都没办法),但是作为初始设计者,至少把初始的设计信息都放进了代码。很多机制都是防君子不防小人的。
另外,好像我不记得这个话题跟gc有啥关系,当时只是针对“对代码进行自动化静态分析”这个话题说的,我要表达的就是很多信息(如果程序员不表达出来的话)是无法分析出来的。

内存,未必可以延后释放。延后释放的问题我之前的帖子里谈了。
如果要让gc通过猜测来把数据放到栈上,那相当于程序员写程序时先把这些信息丢弃,然后再让gc来猜测。还不如像C/C++这样直接让程序员来放置。
rvo貌似现在的C++也有,但是却是有副作用的。
重复使用已分配的内存,这是典型的内存池。
所以这些似乎都不能构成支持gc机制的理由。

而Elminster把对象生命期与内存生命期分离开的想法正好与我相反。如果把这两者分开仅仅是产生一个手动版本的析构函数,那么就失去了析构函数最大的优点:在生命期结束时自动调用。

我之所以要把其他资源绑定到内存上,是因为无论是内存还是对象,都需要有生命期管理,而这其实恰恰是现在管理的最大麻烦之一。只要做好了生命期管理,那么把内存交换给系统/内存池这种事情是不会构成多大的麻烦的,而其他资源的释放一般也只是close()之类的操作。
主要的问题是,应该在何时交换内存、close各种资源。只要有一个机制能够确保:在生命期结束时就及时调用指定的行为,那么内存和各种资源的管理都不会太麻烦。析构函数仅仅是在生命期结束时被调用的那个行为。
生命期的管理,我认为是一个普遍的问题,也应该存在通用的解决方案。所以实现了一个良好的生命期管理机制之后,那么所有的资源管理问题都能够从中获得好处。
栈可以看成一种内存管理机制,但是也可以看成一种生命期模型:它是一种嵌套的、FILO的生命期模型。从这个角度看,对于栈来说,内存管理和对象管理是一样的。


至于那些优化,如果会损及语义,那么很可能是不合算的。
Digg this Post!Add Post to del.icio.usBookmark Post in TechnoratiFurl this Post!
回复时引用此帖
  #90 (permalink)  
旧 2008-03-07
polyrandom 的头像
超级版主
 
注册日期: 2002-09-03
帖子: 3,135
文章: 20
polyrandom 正向着好的方向发展
默认 回复: 请教各位大大关于动态内存管理的问题……

to sjinny:
你说的让gc猜测并不成立。即使在支持GC的情况下,你也可以让某个对象在出了scope以后立即析构的。过去的语言不支持这样做,只是因为它们不支持,不是GC的错。C++/CLI应该就支持这一点。
关于你说的,“至于那些优化,如果会损及语义,那么很可能是不合算的。”这要看你怎么定义语义了。
代码:
//code 1 for( int i = 0; i < 10000; ++i ) GetTickCount(); //code 2 for( int i = 0; i < 10000; ++i ) 0; //code 3 for( int i = 0; i < 10000; ++i ) __asm nop //code 4 for( int i = 0; i < 10000; ++i ) Sleep( 1 );
能否请你告诉我,你心目中理想的编译器,会怎么优化呢?
Digg this Post!Add Post to del.icio.usBookmark Post in TechnoratiFurl this Post!
回复时引用此帖
  #91 (permalink)  
旧 2008-03-07
sjinny 的头像
普通会员
 
注册日期: 2008-02-01
帖子: 66
sjinny 正向着好的方向发展
默认 回复: 请教各位大大关于动态内存管理的问题……

如果我要“让某个对象在出了scope以后立即析构”,那么我会用栈。
我并不是说gc能不能猜出是否有错,我是说这种情况下就应该用栈而不是丢给gc。

我说的“至于那些优化,如果会损及语义,那么很可能是不合算的。”是指ajoo所说的“允许对象进行超光速空间跳跃”。他还给了前提“优化开关打开的情况下”,而如果这种优化会损及语义,那么即使在文档中有所记录也仍然会增加风险。我对编译器优化并不很了解,所以我加了句“如果会损及语义”。

代码:
//code 1 for( int i = 0; i < 10000; ++i ) GetTickCount();
从人的角度看,这段代码没什么意义,所以如果被优化掉也无所谓。但是如果编译器不知GetTickCount的效果也优化掉那么就会损坏语义了,因为这个函数可能会修改一些持续性的状态,而循环调用它的目的也正是修改持续性状态。

代码:
//code 2 for( int i = 0; i < 10000; ++i ) 0; //code 3 for( int i = 0; i < 10000; ++i ) __asm nop
这两段代码看不懂。

代码:
//code 4 for( int i = 0; i < 10000; ++i ) Sleep( 1 );
理论上说,Sleep(1)和Sleep(10000)的效果很可能很不同。比如假设某个操作系统之中,Sleep(1)的意思是“如果别人有事,那么我暂时等一下,如果没人有事那么我会继续”,这个循环就是设置一个特殊的时间段,如果有必要的话才让出CPU;而Sleep(10000)则是等待10秒,是个并不紧急的任务,可能os会把它的优先级调低、停顿会比前者长。我的意思是,有时参数值的不同会使函数的实际效果有所不同,所以这段其实不该优化。


如果要说优化,编译器最好是根据程序员的要求来说,或者只做一些保守的优化。
Digg this Post!Add Post to del.icio.usBookmark Post in TechnoratiFurl this Post!
回复时引用此帖
  #92 (permalink)  
旧 2008-03-07
普通会员
 
注册日期: 2003-08-25
帖子: 80
fixopen 正向着好的方向发展
默认 回复: 请教各位大大关于动态内存管理的问题……

引用:
而Elminster把对象生命期与内存生命期分离开的想法正好与我相反。

唉,又一个被值语义语言“毒害”的人阿。对象的生命周期和内存为什么非得紧密帮定呢?帮定的好处是什么?不知道C+09都要采用move ctor了吗,因为对象和内存帮定对谁都没有好处,除非进行非常危险的指针算术运算。而那东西在最低层的编程才有点用处,所以我们说对象跟内存帮定是弊大于利,对象跟内存绑定,对象的生命周期也就跟内存帮定了,这又是一个看起来很美用起来很傻的特性,其实对象的生命周期可以从逻辑上讲就是构造开始,析构结束。完全可以跟内存的分配和释放无关的。我想所有的内存池都是剥离这个紧密关联的努力的产物。

其实值语义本来未必导致对象跟内存非得是显式的关系,但是值语义的语言确实倾向于促使程序员关注内存这个本来不该关注的东西。
Digg this Post!Add Post to del.icio.usBookmark Post in TechnoratiFurl this Post!
回复时引用此帖
  #93 (permalink)  
旧 2008-03-07
cat cat 当前离线
高级会员
 
注册日期: 2003-11-06
帖子: 1,560
文章: 6
cat 正向着好的方向发展
默认 回复: 请教各位大大关于动态内存管理的问题……

引用:
作者: sjinny 查看帖子
生命期的管理,我认为是一个普遍的问题,也应该存在通用的解决方案。
普遍的问题就有通用的解决方法?未必吧?这是你逻辑中的一个大错,反例太多了,特别是那个“普遍的问题”非常general的时候。
不过就内存管理来说,目前倒是有一个比较通用的解决方案就是GC.

引用:
作者: sjinny
而Elminster把对象生命期与内存生命期分离开的想法正好与我相反。如果把这两者分开仅仅是产生一个手动版本的析构函数,那么就失去了析构函数最大的优点:在生命期结束时自动调用。
在生命期结束时自动调用的话,using/try finally的都做得到。但内存不一定要先在就放了啊。把对象生命期和内存释放绑定在现在的情形下已经没有必要了。

此帖于 2008-03-07 08:00 PM 被 cat 编辑.
Digg this Post!Add Post to del.icio.usBookmark Post in TechnoratiFurl this Post!
回复时引用此帖
  #94 (permalink)  
旧 2008-03-08
sjinny 的头像
普通会员
 
注册日期: 2008-02-01
帖子: 66
sjinny 正向着好的方向发展
默认 回复: 请教各位大大关于动态内存管理的问题……

fixopen,增加的move语义只不过是一种新的优化手段,并不是要替代原有的一切。
之所以要绑定,是为了在管理非内存资源时也能够用上内存管理中现成的生命期管理。把非内存资源绑定到内上只是个手段,目的是为了借用内存管理中的生命期管理功能。

cat,有限数量的反例并不能证明命题在任何时候都不成立。
gc是针对内存的通用的管理机制,但是如果要把其他资源绑定上去,目前的gc还不够好。

我说过了,using貌似提供的就是栈的功能,只不过语法上还不如栈简洁。
而且无论是using还是try-finally,都要求程序员显示给出生命期边界,能用这些东西的地方就不能用栈吗?而对于不能用栈的时候,这些能用吗?对于能用栈的情况,我觉得栈已经足够好了。现在主要的问题是在栈的表达能力以外的领域。所以才有内存池,才有智能指针,才有gc……而它们都不如栈那么简洁优雅。
内存的延后释放我在前面的帖子里说过的,你感兴趣的话可以翻到前面几页看看。
Digg this Post!Add Post to del.icio.usBookmark Post in TechnoratiFurl this Post!
回复时引用此帖
  #95 (permalink)  
旧 2008-03-08
高级会员
 
注册日期: 2002-09-15
帖子: 2,531
ajoo 正向着好的方向发展
默认 回复: 请教各位大大关于动态内存管理的问题……

引用:
作者: sjinny 查看帖子
花括号的第一个作用是表达作用,即使会依赖于程序员之间的约定,但是这仍然能够用来表达一些信息,这是为了以后的理解和维护而着想的。以后的需求会变,但是在改动代码之前,至少应该知道这段代码以前的设计情况。
你那段代码的确可以把running泄漏到外面去,但是如果是一开始写代码的人这么做的,那么只能说这是有意而为之,这是初始设计者所设计的;如果是维护者的修改,那么至少代码本身能够表达这样的信息:维护者把一个在最初设计中的局部变量泄露到了最初设计的作用域之外。而如果代码没有那么写,那么维护者根据什么来了解最初的设计?如果没有了解到最初的设计,那么如何判断修改的风险?
我始终不明白你这个“意图”论是什么意思。
代码:
{ Integer i = new Integer(1); Integer j = i; print(j); }
这个意图哪里不清楚了?有什么东西需要编译器去“猜”了?所谓“修改的风险”又指什么?能不能麻烦举个具体的例子?

引用:
作者: sjinny 查看帖子
内存,未必可以延后释放。延后释放的问题我之前的帖子里谈了。
大家不理会你那个“谈话”就说明大家不认可。你的结论和论据之间跳跃过大,要不迁就一下我们,放慢思考的速度?


引用:
而Elminster把对象生命期与内存生命期分离开的想法正好与我相反。如果把这两者分开仅仅是产生一个手动版本的析构函数,那么就失去了析构函数最大的优点:在生命期结束时自动调用。
早八百年我就在这个坛子上说拷贝构造和析构都是看上去很美,但是有毒的东西。它把内存管理和其它资源的管理混为一谈,不仅仍然没有作好资源管理的工作,反而给自己带上镣铐,让编译器的优化不能尽其所长,让gp的编写很难通用(因为在函数边界传递一个T这么一个简单的动作其后果是不可预料的)。当时好像所有人都反对我吧?嘿嘿,现在看来情况有所好转啊。

引用:
我之所以要把其他资源绑定到内存上,是因为无论是内存还是对象,都需要有生命期管理,而这其实恰恰是现在管理的最大麻烦之一。
不同的东西的生命期管理有不同的特点。

引用:
生命期的管理,我认为是一个普遍的问题,也应该存在通用的解决方案。所以实现了一个良好的生命期管理机制之后,那么所有的资源管理问题都能够从中获得好处。
栈可以看成一种内存管理机制,但是也可以看成一种生命期模型:它是一种嵌套的、FILO的生命期模型。从这个角度看,对于栈来说,内存管理和对象管理是一样的。
内存管理远远不是filo这么简单。

引用:
至于那些优化,如果会损及语义,那么很可能是不合算的。
两点:
1。优化是重要的。一个损及优化能力的语言语义模型是失败的,尤其对c++这种号称关注系统级编程的语言。
2。语义的一致和完整性是重要的。为了优化而不惜破坏语义完整性(如c++的rvo)是可怕的。

综上,既然优化没错,语义完整性也没错,那就是c++错了,是妄图把内存管理和副作用混为一谈错了。
Digg this Post!Add Post to del.icio.usBookmark Post in TechnoratiFurl this Post!
回复时引用此帖
  #96 (permalink)  
旧 2008-03-08
sjinny 的头像
普通会员
 
注册日期: 2008-02-01
帖子: 66
sjinny 正向着好的方向发展
默认 回复: 请教各位大大关于动态内存管理的问题……

我前面那个例子就是:
代码:
for( bool running= true; running; ){ switch(){ ... } }
还有,我那个针对的是代码的表达能力,针对的是代码的静态分析……这些都是为人服务的……
编译器不用猜测人的意图,但是人自己需要知道……编译器不需要知道在初始设计之中那个running的含义和使用范围,更不需要知道以后修改时需要维持哪些约束、哪些约束是可以放弃的。但是人需要知道,所以在代码里表达出来会更好。

我不认为构造和析构函数把内存管理和其它管理混为一谈。构造函数和析构函数仅仅是提供了一种手段,这种手段让你能够把你所期望的行为绑定到某个内存块的生命期边界上:生命期开始时调用构造函数,结束时调用析构函数。构造函数和析构函数并不是专门用来管理资源的,它的使用效果取决于你的选择。就好象多线程中给mutex加锁的操作。一个Guard对象并不是管理mutex的,而仅仅是为了提高加锁/解锁的自动化程度以减少出错的机会。构造函数和析构函数仅仅是把行为绑定到内存块生命期边界上的手段。
至于你所说的在传递T时会有无法预知的行为的问题,这不是构造函数或析构函数的问题,这是抽象引起的问题。当然其中还有值语义的因素,但是这本身也可以看作一种抽象。如果很多种不同的对象被使用时必须要有各自的初始化行为,那么当这行为被放进构造函数时,它们可以用相同的形式来传递;如果没有放进构造函数,那么这些无法省略的初始化行为就不得不在函数内显式调用,那么问题就在于:
1.不同类的初始化接口可能不相同;
2.函数编写者可能会忘记调用初始化接口。
3.当进行GP时,你如何知道这个T是否应该调用初始化接口呢?
构造函数提高了GP的抽象能力。
我不记得模板能不能限制或识别模板参数是否为指针了,似乎用TypeTraits可以做到。但是,即使无法做这种区分或限制,那么:
传入的T是什么,取决于这个模板的使用者。如果使用者需要使用引用语义的传递,那么可以选择把指针类型传给模板,或者做个wrapper。
就好象一个电磨,可以绞肉,也可以打果汁,电磨只知道把放进去的东西搅碎,至于其语义,那是使用者的事情。如果一个模板需要对外界有很多的了解,那么意味着在模板面前的世界的抽象度相对比较低,那还不如用普通的函数和类。

值语义有时会有缺陷,但是它有自己存在的意义和必要性。新标准引入的move语义也并不是值语义的替代。我个人的体会是,值语义比引用语义更容易使用,因为它把一次模块交互的两端(即传递值的源模块和目的模块)分开了,交互的两个模块间没有物理性的关联,传入的值再怎么变动也不会影响到源模块。如果传递的是指针,那么模块间的耦合度就变高了,毕竟目的模块无法得知源模块是否会继续使用这个对象,那么要么就让模块间的了解变多(增加耦合),要么就是增加风险(目的模块对传入的对象的误用可能会引起源模块的错误,甚至正常使用也不是100%安全的)。所以我现在都是尽量使用值语义,即使我知道会有更多的复制,但是在我profile之前我没有任何根据来优化,而profile之后也可以用内存池或其他的手段来优化。

不同的东西的生命期管理有不同的特点。
这点我自然会承认,但是这点并不能证明“不同的东西的生命期管理没有共性”吧?而我要把其他资源的管理绑定到内存上,就是为了使用内存管理中现成的生命期管理功能,更具体的说,借用的无非就是生命期边界的管理功能。这个边界就是生命期的开始和结束。我假设任何资源的生命期都只会有一次开始和一次结束。如果不符合这个假设,那么我觉得完全可以把那种生命期分解为这种结构。比如,如果要复用对象,那么只需要把对象的一次使用看作一个完整的生命期;比如mutex加锁,mutex存在的过程中会有多次加锁/解锁,但是却可以分解为多次Guard的构造和析构。这种“一次生命期中只有一次开始和一次结束”的模型虽然不能表达所有的情况,但是是一种有效的工具。而这种模型就是我所认为的各种资源的生命期管理的共同点。

至于内存管理和FILO的问题。我那段话说明的只是栈的特点,如果某些栈在编译器优化之前不遵循FILO的约定,那么请告诉我……
其实我那段话只是把栈拿来作个例子,用以说明从生命期的角度来看到的栈。重点是把栈作为生命期模型的实现,至于FILO则是次要的。

关于优化,我认为作为语言和工具,要提供的只是优化的手段和保守的自动化优化。优化的根本还是在于profile和程序员的分析。rvo貌似是不得已而为之的,move语义的出现应该会成为rvo的替代,或者说能够使rvo时函数的语义与语法一致。
而你说的这句:“一个损及优化能力的语言语义模型是失败的”,我的理解是你在说C++的语义模型是损及优化能力的,那么能不能具体的说说C++的什么语义损及了优化能力呢?又有什么语言能够在对应的方面有着不损及优化能力的语义呢?毕竟我没有在这句话中看到“如果”二字。

如果因为一个rvo就能把整个语言否定掉,那我也无话可说了。反正世界上不会有完美的语言,那么任何语言都能因为局部的问题而否定掉全局。

最后那句“把内存管理和副作用混为一谈”中的“副作用”是指什么?我不明白如果一个效果被人认为是副作用,那么这人还会有意地去把内存管理和这种副作用混合起来吗?


PS.感觉我经常照着别人的帖子一段一段的回复,而我得到的经常只是无穷的反问,甚至发问者似乎没有仔细看我的文字。这样很累。
Digg this Post!Add Post to del.icio.usBookmark Post in TechnoratiFurl this Post!
回复时引用此帖
  #97 (permalink)  
旧 2008-03-08
bankrock 的头像
高级会员
 
注册日期: 2003-12-11
帖子: 843
文章: 7
bankrock 正向着好的方向发展
默认 回复: 请教各位大大关于动态内存管理的问题……

居然又开始了,这个问题这么火?
说下我的民工看法,其实C++已经把RAII推给程序员了,就没有不用的道理,但是最好还是在核心库的层面解决掉内存管理的问题,这样用起来就爽多了,GC我觉得在C++里有点画蛇添足。
其实一个程序最重要的还是它自身代表的逻辑,其他的东西,不管是GC,RAII都需要为这个逻辑服务,如果没有GC会导致某个C++程序的逻辑变得支离破碎,那还不如换其他的语言好些(虽然这种事情我还没见过)。
Digg this Post!Add Post to del.icio.usBookmark Post in TechnoratiFurl this Post!
回复时引用此帖
  #98 (permalink)  
旧 2008-03-08
高级会员
 
注册日期: 2002-09-15
帖子: 2,531
ajoo 正向着好的方向发展
默认 回复: 请教各位大大关于动态内存管理的问题……

等待有好事者跳出来帮我回答“副作用”的问题...
Digg this Post!