ExcelHome技术论坛

 找回密码
 免费注册

QQ登录

只需一步,快速开始

快捷登录

搜索
EH技术汇-专业的职场技能充电站 名课 - Power BI数据分析与可视化实战 Excel服务器-会Excel,做管理系统 效率神器,一键搞定繁琐工作
Python自动化办公应用大全 Excel 2021函数公式学习大典 Kutools for Office 套件发布 打造核心竞争力的职场宝典
让更多数据处理,一键完成 数据工作者的案头书 免费直播课集锦 ExcelHome出品 - VBA代码宝免费下载
用ChatGPT与VBA一键搞定Excel WPS表格从入门到精通 Excel VBA经典代码实践指南
楼主: LIUZHU

[求助] 怎样正确释放存放了对象的数组变量

[复制链接]

TA的精华主题

TA的得分主题

发表于 2014-3-7 14:03 | 显示全部楼层
LIUZHU 发表于 2014-3-7 13:50
这个道理我知道,所以引用类库时会特别注意,一定会引用和低版本Excel兼容的那个类库。

这问题比较复杂,数组里面装对象可能是引用的引用。如果是动态数组,那么数组本身就是引用类型,数组里面装的内容其实是对象的引用。你使用Erase arr 的时候是把包含对象引用的数组给清掉了,但是对象实体的内存没有被清掉,而且失去引用的它们所在的内存空间再也无法被清掉。可能只有在Application 被关掉的时候才会被释放。失去引用的对象实体就是著名的内存垃圾,而Java 、C# ,vb.net 垃圾收集机制就是系统在适当的时候自动回收这部分已经沦为垃圾的内存。而vb 语言是没有这种机制的,因此当内存对象一旦沦为垃圾,程序员就再也没有任何访法访问到乃至释放这部分内存了。

这个问题可能画个内存示意图更容易讲明白。

TA的精华主题

TA的得分主题

发表于 2014-3-7 14:07 | 显示全部楼层
LIUZHU 发表于 2014-3-7 13:51
您的意思是前期绑定在SUB过程结束时,空间就一并被释放了?

差不多,还有几种情种,有SUB外的准全局变量,不过你应该理解这种东西。
下面这两种是有区别的
假定你引用了类库my.xx
可以生成类型为XX的实例——当然具体这个MY可以是ADODB啦,SCRIPTING,那个XX可以是字典,可以是文件,可以是记录,

SUB TEST()
DIM A AS NEW XX
  DIM B AS  XX
  SET B=NEW XX
END SUB

其中A就是即时生成实例,而B只是一个前期限定为MY.XX类型的指针(它只是强行限定),在后一句再生成实例,同样是用了引用库,B还是可以释放再引用的——也就是说,它可以相对灵活地指向XX类型的其它实例,
在这个例子里,它和A的生存周期都是在SUB TEST,但是呢,如果你做一个父程序,上传一个BY REF的XX实例给上面,实例的生存周可能更长

FUNCTION  test() as xx
DIM  B AS XX
SET B=NEW XX  '<==生成了一个实例

SET test=B  '<==函数结束时,B的生存周期结束了,但实例传给了函数作为结果
END FUNCTION

TA的精华主题

TA的得分主题

发表于 2014-3-7 14:24 | 显示全部楼层
hiyou 发表于 2014-3-7 14:07
差不多,还有几种情种,有SUB外的准全局变量,不过你应该理解这种东西。
下面这两种是有区别的
假定你引 ...

比如上面的例子:
SUB TEST()
DIM A AS NEW XX
   DIM B AS  XX
   SET B=NEW XX
END SUB
无论A是即时生成也好, B 是后绑定时生成也好。A和B 都是内存中的一个引用,或者换句话说是一个指针指向对象实体,那么这个对象实体在哪里?和A,B 是不在一起的。

众所周知 A,B 属于Sub 内的局部变量,系统开这个Sub 的时候就会在内存中开辟一个栈区(Stack) 这个栈区是随着Sub 的结束而被系统自动回收的这种叫做清栈操作,这是栈的管理特性所致。 而对象实体是开辟在堆内存(Heap)里面的,堆内存中是程序员开辟的,比如前面的New xxx 或者CreateObject (), 这些对于内存来说没分别都是在堆内存中开辟区域生成对象实体。程序员是无法直接操作堆内存的对象实体的,只能通过该实体的引用,即栈中的引用变量A,B来访问对象。当A,B 的生存期结束时,比如执行到End Sub 语句。那么堆内存中的对象实体则将失去引用成为垃圾,只有垃圾收集机制能自动回收这部分内存。支持垃圾回收机制的开发工具很多比如C#,vb.net,Java 等等,很不幸不包含VB。因此为了避免垃圾太多造成的内存泄漏,所以通常都要求程序员在用不到的内存对象变成垃圾之前手动回收它。比如C 语言的 free 语句,C++ 的delete 语句还有VB 的Set xxx= nothing。

所以清除对象实体是与前后绑定无关的,只要在堆内存中产生了对象实体,而在使用完毕后没有及时回收,当它失去引用后就即刻变成了垃圾,而埋下内存泄漏的隐患。

TA的精华主题

TA的得分主题

发表于 2014-3-7 15:09 | 显示全部楼层
LIUZHU 发表于 2014-3-7 13:56
我一直不喜欢用后期绑定的原因在于,前期绑定似乎让程序运行得稍微快点,另外用后期绑定时,使用有些属性 ...

确实如此。
原因是前期绑定已经声明了类型,代码在运行前——或者说“编译前”(虽然VBA有时可以理解为解释型,但它是可以“编译前运行”)把变量定型,如果没有前期声明它的类型,它就是VAR万用型,在运行中还要做一次类型判定,大量数据调用的时候延时会明显。

但这里有取舍的情况,如果这类变量只使用几次——比如说明确知道它是“在程序开始只调用3-10次”,那么此变量的效率和时间就不是问题,后期绑定也无所谓。

会出错要看默认属性,不是很清楚,比如对象引用,在VBA里不指定类型它会有默认的属性代入,大部分是ITEM啦、VALUE等类型,你给一些实际的代码看看。

说到这里,我想起来前期绑定的另一个好处是给明确了类引用的对象声明事件,象下面这样

DIM WITHEVENTS A AS XX  ‘《==给A对象加上事件,后面可以预先定义XX类的事件

TA的精华主题

TA的得分主题

发表于 2014-3-7 15:34 | 显示全部楼层
hehex 发表于 2014-3-7 14:24
比如上面的例子:
SUB TEST()
DIM A AS NEW XX

垃圾回收机制是动态的,但在自动化对象中,它们的管理与VB关系不大。举同样的例子来说。
SUB TEST()
DIM A AS NEW XX  ‘《==定义一个实例对象,这里再说明一下,这一行它还不出现实例
DIM B AS XX   
DIM C AS XX        ’〈==B和C定义了“指针”,注意到此为止,A,B,C都是空的,没有实例

SET B=NEW XX   ‘〈==生成一个实例
B.MYSUB  '<==XX类的对象有一种叫MYSUB的方法

SET C=NEW XX   ’〈==生成一个实例
'到此为此B和C在内存中生成了两个实例

A.MYSUB    '<==用了A实例的一个方法MYSUB,此时生成A实例,并且调用XX类的方法

SET C=B  ‘〈==注意这一步,C指向B,它们共用同一个对象实例,原来C指向的对象被“弃用”了

SET  B=NOTHING
SET  C=NOTHING
END SUB


看看上面例子的几行代码,我都加了说明。其中一个是NEW A的说明,我在前面有点疏忽错误——MS VB的风格是这样,它的实例NEW是在第一次调用才生成。

然后就是生成B和C两个实例。下一步C指向B的实例,就产生了“可能内存泄漏”的问题,
自动化对象的处理前面说过,如果计数为0,它自已释放,如果不是——也就是还有指向它的引用,它继续生存(还有一种情况就是EXE有管理的实例,自已在EXE上一级关闭前主动强行杀掉)
这种行为是正常的。
所以呢,这个C原来NEW出的XX如果是外部的“WINDOWS注册的类库”,它自已可能就自我销毁了,但如果管理不善的,它就留在那里,

那垃圾管理机制呢,其实是一样的,只不过有各种算法在后台帮你处理,一句话,就是说在VB或VBA里你用SET C=NOTHING再SET C=B 比较放心,在JAVA里堂而皇之用上面几句完全推给后台眼不见为净了。
(真正的问题一样会有,循环引用之类,不是自动动态算法能解决的)

TA的精华主题

TA的得分主题

发表于 2014-3-7 16:21 | 显示全部楼层
本帖最后由 hehex 于 2014-3-7 16:26 编辑
hiyou 发表于 2014-3-7 15:34
垃圾回收机制是动态的,但在自动化对象中,它们的管理与VB关系不大。举同样的例子来说。
SUB TEST()
...

正如你指出的例子,
SET B=NEW XX   ‘〈==生成一个实例
B.MYSUB  '<==XX类的对象有一种叫MYSUB的方法

SET C=NEW XX   ’〈==生成一个实例
'到此为此B和C在内存中生成了两个实例

这时候,B 和C 在栈内存中,他们的实体生成在堆内存中。
而当出现 Set C = B ' 这句话之后,VB 的环境中已经出现了一块内存垃圾。即栈内存中C 所引用的堆内存中的原对象实体已经失去引用了。
后面Set B = Nothing ' 释放了B 引用的对象在堆内存中所占的内存区域
Set C = Nothing ' 无意义,现在C 引用已经没有指向实体了
而C 引用原来所指向的实体残存在堆内存中,没有人能再访问到它,释放掉它,沦为了一堆垃圾。

"自动化对象的处理前面说过,如果计数为0,它自已释放,如果不是——也就是还有指向它的引用,它继续生存(还有一种情况就是EXE有管理的实例,自已在EXE上一级关闭前主动强行杀掉)
这种行为是正常的。
所以呢,这个C原来NEW出的XX如果是外部的“WINDOWS注册的类库”,它自已可能就自我销毁了,但如果管理不善的,它就留在那里,"


很遗憾上面这些话是完全没有根据的, 正相反内存的垃圾定义恰恰是失去引用的对象实体。
按照你上面的理论,如果是前绑定即所谓的New 出来的对象,或者是"Windows 注册的类库" 在它失去引用的瞬间,自我检测发现它已经成为垃圾了,从而自我销毁释放掉了。这在堆中是没有实现的,如果将来会实现,那么可能是一种更高级的垃圾回收机制。在Java,C#等语言中目前比较成熟的垃圾回收机制也不过是当系统的内存消耗到一定比例,或者当系统认为比较适当的时机。开始对堆进行扫描,当发现失去引用的对象实体时(垃圾),回收掉。也没有做到即刻动态回收内存。

更何况VB 是一种没有垃圾回收机制的语言,Windows 上也不能直接实现。Java 是运行在JVM 上的,c# 和vb.net 是通过.net 框架,从某种意义上说.net FrameWork 也是一种虚拟机。


TA的精华主题

TA的得分主题

发表于 2014-3-7 16:32 | 显示全部楼层

TA的精华主题

TA的得分主题

发表于 2014-3-7 16:41 | 显示全部楼层
[广告] VBA代码宝 - VBA编程加强工具 · VBA代码随查随用  · 内置多项VBA编程加强工具       ★ 免费下载 ★      ★使用手册
需要注意的是:

如果VBA代码中引用字典对象采用了前期绑定的代码写法,
那么如果把这个代码提供给别的用户、别的电脑使用时,会有环境不同无法引用字典对象的可能性。

因此面向一般用户的通用代码,还是建议使用后期绑定。
前期绑定的好处一般是编程调试时需要、成熟的代码完全可以避开没有进行前期绑定时的缺点。

TA的精华主题

TA的得分主题

发表于 2014-3-7 16:43 | 显示全部楼层
有些问题,自己试一试也能明白的。

TA的精华主题

TA的得分主题

发表于 2014-3-7 16:55 | 显示全部楼层
[广告] VBA代码宝 - VBA编程加强工具 · VBA代码随查随用  · 内置多项VBA编程加强工具       ★ 免费下载 ★      ★使用手册
hehex 发表于 2014-3-7 16:21
正如你指出的例子,
SET B=NEW XX   ‘〈==生成一个实例
B.MYSUB  '

垃圾回收机制的依据,是"更强大的系统以一定的代价在后台代替手工自动回收内存对象",这不是没有代价的,但这不是讨论什么系统,什么机制的问题,孩子没娘说来话长——在早期的系统,JAVA和.net的虚拟机制本身实现就很困难,不是说这种先进的理念为什么程序员没想到,那会儿为了效率和速度在和硬件战斗呢,虚拟机与垃圾回收理念简直是反人类,不,是反程序的大罪啊。

至于自动化对象还是某个VBA的对象,这里有点歪楼了。楼主开始提问的是具体的RANGE,它是EXCEL——也就是VBA中APP的对象,而进程外的自动化对象,它还真不是VBA能管到的。

举个例子,嵌入工程的WEB控件引用,和引用进程外的NEW IE对象,后者即使在整个程序退出,它也可以作为EXE存在——那可不是一个SET IE=NOTHING 可以搞定的,它自已就是一个自动化服务,你要用另一个它认定的方法退出。
您需要登录后才可以回帖 登录 | 免费注册

本版积分规则

手机版|关于我们|联系我们|ExcelHome

GMT+8, 2025-12-25 06:10 , Processed in 0.029475 second(s), 9 queries , Gzip On, MemCache On.

Powered by Discuz! X3.4

© 1999-2023 Wooffice Inc.

沪公网安备 31011702000001号 沪ICP备11019229号-2

本论坛言论纯属发表者个人意见,任何违反国家相关法律的言论,本站将协助国家相关部门追究发言者责任!     本站特聘法律顾问:李志群律师

快速回复 返回顶部 返回列表