首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >VBA 函数突然返回空串,没有任何报错:CLng 只有 21 亿上限,而溢出被 On Error 吞了

VBA 函数突然返回空串,没有任何报错:CLng 只有 21 亿上限,而溢出被 On Error 吞了

原创
作者头像
用户12775403
修改于 2026-09-22 11:18:29
修改于 2026-09-22 11:18:29
750
举报

起始状态:一个多机同步的 Excel 业务系统。为了让副本知道"母版有没有变",我给母版文件算一个内容指纹(滚动哈希 + 文件大小),副本比对指纹决定是否更新。函数写完,小样本测试通过;一上真实文件(1.7 MB),返回空串。

#WorkBuddy

一、现象:小文件正常,大文件返回空

指纹函数长这样(简化版):

倒数第四行那个 If h > 1E+15 ... 是我当时的"取模":想着数值太大就减掉一个 1E+15 的倍数,防止越界。

测试时我拿一个小文件跑,返回 0088343845-1668424,看起来完全正常。

换成真实的 1.7 MB 文件,返回 "-"——哈希部分整段消失了。

没有任何报错。 因为开头那句 On Error Resume Next 把溢出错吞了:循环里某一步赋值失败,h 变成 0,函数照常往下走,最后拼出一个残缺的字符串。

二、根因:CLng 的上限是 2,147,483,647,不是 1E+15

VBA 的 Long 是 32 位有符号整数,最大值 2,147,483,647(约 21 亿)。

上面那句"取模"的阈值写成了 1E+15。这意味着:

  • 循环中途 h 早就超过 21 亿 → CLng(...) 溢出报错 → 被 On Error 吞掉,h 保持上一次的值或直接归零;
  • 就算侥幸没溢出,只要循环结束时 h 落在 21 亿 ~ 1E+15 之间,h > 1E+15 这个条件永远不成立,"取模"根本不会执行,h 就是一个越界的无效值。

两条路都通向"返回空/错值"。

而我的小样本测试之所以"通过",纯粹是巧合:那个文件的哈希值算完只有 88,343,845,远低于 21 亿,两条雷都没踩到。

这是我这次最想强调的一点:用小块数据验证通过,不等于正确。 数值类代码必须用真实规模的数据验证,否则你验证的只是"恰好没触发边界"的那一种情况。

三、修复:每步都取模到 [0, 2^31)

不要"等它变大了再取模",而是每一步乘法之后立刻取模:

三处改动:

  1. 阈值改成 2,147,483,647,和 Long 的上限对齐(代码里的常量 FP_MOD);
  2. 每步取模,不让中间结果有机会越界:先转成 Double 做乘加,取完模再转回 Long;
  3. On Error Resume Next 改成 On Error GoTo Fail,失败时走明确的错误分支,返回空串让调用方能判断,而不是静默产出错误值。

改完实测:1.7 MB 文件 0.23 秒算完,连续两次调用结果一致。

四、排查过程:为什么一开始没找到

这里有个过程上的坑,比 bug 本身更值钱。

我第一反应是"逻辑写错了",写了个分步探针去打每一步。但探针是这样跑的:

第一行用 VBIDE 把探针代码注入工程,第二行在同一个会话里立刻运行它。

结果 Excel 直接崩了(RPC_E / 远程过程调用失败)。后来才知道:用 VBIDE 改完代码后,同一个会话里立刻 Application.Run 可能失败。

正确姿势是 注入 → 保存 → 关闭 → 新会话打开 → 再 Run。

更关键的一招:让被注入的诊断过程把每一步结果写进日志文件,而不是只靠返回值。

这样即使 Excel 中途崩了,也能看到断在哪一步。"崩溃 + 有日志"远好过"崩溃 + 什么都没有"。

五、四个失败预警

  1. On Error Resume Next 会掩盖溢出、类型不匹配、下标越界这三类错。 数值计算密集的函数里别用它;真要用,至少在关键赋值后检查值是否合理。
  2. VBA 没有 LongLong(32 位 Excel 上),Long 上限就是 21 亿。任何"累加/乘倍数"的算法都要算一下量级:1.7 MB 文件逐字节乘 33,不取模的话第 6 个字节就爆了。
  3. 用 CDbl 做中间态再取模,不要指望 Long 自己扛住。
  4. 验证必须用真实规模数据。 这次如果只拿小文件验证,我会得出"函数没问题"的错误结论,然后去排查完全不相干的地方。

六、顺带的两个经验

  • 判定"两个文件是否一致",优先用内容指纹,不要用时间戳或版本号。 时间戳会因为复制、同步、时区而漂移;版本号要额外维护,也会忘记更新。内容指纹只认字节,1.7 MB 文件 0.23 秒,完全够用。
  • "已同步"标记要在动作成功之后再写。 我原来的代码是在替换动作之前就把状态记成"已同步",一旦后面的复制失败,副本就被永久盖章为"最新",再也不会更新——这是一个"失败即留下永久错误状态"的设计,而且无法自愈。改成"由执行方在成功后写标记"之后,失败就会自动重试。

七、小结

症状

根因

修法

数值函数返回空/错值且无报错

CLng 溢出被 On Error Resume Next 吞掉

每步取模到 [0, 2^31);改用 On Error GoTo

小样本通过、真实数据失败

小数据恰好没触发 21 亿边界

用真实规模验证

注入 VBA 后 Run 直接崩

同会话内改码后立刻运行不稳定

注入 → 保存 → 关闭 → 新会话 → Run

崩溃后无从下手

诊断逻辑只靠返回值

让诊断过程把每步写进日志文件

一句话:VBA 的静默失败,几乎都和 On Error Resume Next 有关。 它让代码"看起来在跑",其实在错误地跑。

#WorkBuddy

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

目录
  • 一、现象:小文件正常,大文件返回空
  • 二、根因:CLng 的上限是 2,147,483,647,不是 1E+15
  • 三、修复:每步都取模到 [0, 2^31)
  • 四、排查过程:为什么一开始没找到
  • 五、四个失败预警
  • 六、顺带的两个经验
  • 七、小结
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档