【问题标题】:VBA takes wrong branch at If-statement - severe compiler bug?VBA 在 If 语句中采用错误的分支 - 严重的编译器错误?
【发布时间】:2021-03-10 12:02:31
【问题描述】:

标题中的问号只是因为我非常不愿意将任何事情称为编译器错误,但在这种情况下,如果有人能以任何其他方式解释这种行为,我会感到惊讶。

重现问题的代码非常非常简单。在标准模块中,我们有以下内容:

Sub CompilerBug()
    Dim oClass As cClass
    Set oClass = New cClass

    If False Then
        Debug.Print "This doesn't print, as it shouldn't."
    End If

    If Falsee(oClass.Clone) Then
        Debug.Print "This does print, although it shouldn't!"
    End If
End Sub

Public Function Falsee(oClass As cClass) As Boolean
    Falsee = False
End Function

我们在名为 cClass 的类模块中定义了一个类 (cClass),包含以下代码:

Public Function Clone() As cClass
    Dim oClass As cClass
    Set oClass = New cClass
    Set Clone = oClass
End Function

Private Sub Class_Terminate()
End Sub

代码是不言自明的。 第二个 if 语句被输入,尽管函数 Falsee 返回 False,无论输入如何!当类中的函数被替换为类似的Public Property Get时,可以观察到相同的结果。

出于复制的目的,我在我的 Office 365 Excel,64 位,版本 2011(内部版本 13426.20274)中遇到了这种行为,这是当前最新版本的 excel。我还在我的 Word VBA IDE 中测试了这个确切的代码,结果完全相同。

“证明”:

我不知道是什么导致了这种行为,但这里有一些线索:

如果我们将 sub 中的代码重写为:

Sub CompilerBug()
    Dim oClass As cClass
    Set oClass = New cClass

    Dim bFalse As Boolean
    bFalse = Falsee(oClass.Clone)

    If bFalse Then
        Debug.Print "This doesn't print, as it shouldn't."
    End If
End Sub

(为简洁起见,省略了第一个 if 语句。)代码按预期执行,因此在 if 的条件下直接调用函数至关重要 声明(通常不应该有所作为)。

下一个有趣的线索如下(假设我们再次使用带有 If Falsee(oClass.Clone) Then 的错误子代码): 如果我们从类模块中删除以下内容:

Private Sub Class_Terminate()
End Sub

if 语句按预期工作,并且没有打印任何内容!因此,在评估 If 语句期间执行的 Terminate 事件以某种方式将事情搞砸了,但 Class_Terminate() 子甚至不包含任何代码!接下来的事情不应该有所作为!

这个想法得到了进一步的支持,当我们在模块中声明一个公共变量时,在顶部添加Public poClass As cClass并将函数代码重写为:

Public Function Falsee(oClass As cClass) As Boolean
    Set poClass = oClass
    Falsee = False
End Function

现在在执行 If 语句期间不会调用 Terminate 事件,因为在执行 If 语句期间类的实例不会超出范围,因此,If 语句正确评估 - 行没有被打印出来。

显然,在评估 If 语句期间执行的 Terminate-event 不可能是全部,因为这种情况一直在发生。它似乎也与被终止的对象的范围以及参数传递给函数的方式有关。例如,以下不会产生相同的行为:

模块代码:

Sub CompilerBug()
    Dim oClass As cClass
    Set oClass = New cClass

    If Falsee(oClass.CreateAndDestroyObject) Then
        Debug.Print "This doesn't print, as it shouldn't."
    End If
End Sub

Public Function Falsee(lng As Variant) As Boolean
    Falsee = False
End Function

在类模块中:

Public Function CreateAndDestroyObject() As Long
    Dim oClass2 As cClass
    Set oClass2 = New cClass
    Set oClass2 = Nothing
End Function

Private Sub Class_Terminate()
End Sub

总而言之,该行为发生在以下情况:

一个类的方法返回同一个类的一个实例,并且这个方法在一个 If 语句的条件内被调用,作为一个函数的参数,并且这个类的实例(由该方法创建) 然后超出该函数内部的范围,并且类的终止事件被调用(并作为代码存在)。 在这种情况下,无论函数的返回值如何,都会输入 if 语句。

对我来说,还有很多问题......为什么 Terminate 事件在这种情况下会产生影响?我的任何代码是否应该产生未定义的行为,或者这实际上是一个错误?是否还有其他情况下 If 语句不能按预期方式工作?究竟是什么导致了这个错误?

在 32 位版本的 Excel 中似乎不存在该问题。

【问题讨论】:

  • 在 Excel O365 32 位中,我无法重现您的问题。什么都没有打印,并且逐行执行,每一行都执行。
  • @RonRosenfeld 我现在也在另一台 PC 上尝试了 32 位版本,我也无法在那里重现它。我也刚刚问了一位同样运行 64 位版本的同事,他能够重现它。
  • 很奇怪。不会想到“位”会在这样的事情上有所作为。
  • 不精通 VBA,但这可能与将“false”拼写为“falsee”有关吗?
  • @TylerH 没有。 VBA if 语句的工作方式如下:如果条件评估为True,则输入该语句。如果条件计算结果为False,则语句跳转到下一个ElseIfElseEnd If,以先到者为准。 (当然,忽略 If False Then 和下一个 ElseIf 部分之间的嵌套 if 语句......)它与评估“成功”的函数无关。如果函数不成功,如果没有适当的错误处理,程序将抛出错误。

标签: excel vba oop if-statement compiler-bug


【解决方案1】:

此错误在 32 位上不存在,但在支持 64 位 VBA 的应用程序中似乎存在(我尝试过 Excel、Word 和 AutoCAD)。

由于问题已经涵盖了如果对象没有终止或没有Class_Terminate 事件会发生什么,以下示例都使用肯定会超出范围的对象,我们还假设存在@987654324 @ 被调用:

Option Explicit

#If Win64 Then
Sub Bug()
    ' We don't really need a Clone method to reproduce the bug
    If Falsee(New cClass) Then
        Debug.Print "This does print, although it shouldn't!"
    End If

    ' If we add a logical operator and a second method call then the bug disappears:
    If Falsee(New cClass) Or Falsee(New cClass) Then
        Debug.Print "This doesn't print, as it shouldn't."
    End If

    ' It could be any other method. The order of the methods also doesn't matter
    If Falsee(New cClass) Or Sin(0) Then
        Debug.Print "This doesn't print, as it shouldn't."
    End If

    ' The above workaround does not work if we simply use a boolean value after the method call
    If Falsee(New cClass) Or False Then
        Debug.Print "This does print, although it shouldn't!"
    End If

    ' But it does work if we add the boolean before the method call:
    If False Or Falsee(New cClass) Then
        Debug.Print "This doesn't print, as it shouldn't."
    End If
    If True And Falsee(New cClass) Then
        Debug.Print "This doesn't print, as it shouldn't."
    End If
End Sub

Function Falsee(oClass As cClass) As Boolean
    Falsee = False
End Function

#End If

【讨论】:

  • 有趣,这让事情变得更糟!此外,如果您让函数返回其他内容,例如字符串,然后在比较中使用它,则会出现错误:函数内部:Falsee = "Falsee" 和 if 语句中:If Falsee(New cClass) = "True" Then -> if-语句将被输入。
  • 注意:虽然通常不接受非解决方案作为答案,但提供额外信息或进一步隔离或定义问题的部分答案,如果问题尚未得到解决,欢迎这样做:“通过研究问题帮助我们找到解决方案,然后贡献您的研究结果以及您尝试过的任何其他内容作为部分答案。这样,即使如果我们无法弄清楚,下一个人还有更多工作要做。” (source)
【解决方案2】:

确实是64位版本VBA7的bug。这是一个较小的例子:

SomeClass.cls:

Private Sub Class_Terminate()
End Sub

Main.bas:

Function ReturnFalse(o As Object) As Boolean
 ReturnFalse = False
End Function
Sub test()    
 Debug.Print ReturnFalse(New SomeClass)
 If ReturnFalse(New SomeClass) Then
  Debug.Print "True"
 Else
  Debug.Print "False"
 End If     
End Sub

打印出来

False
False

关于 32 位 VBA 和

False
True

关于 64 位 VBA

我可以在将近三年前的 uservoice 帖子 (https://excel.uservoice.com/forums/304921-excel-for-windows-desktop-application/suggestions/35735881-fix-inlined-member-calls-on-user-objects-on-64-bi) 中找到有关此错误的报告,但此后没有任何反应。此外,显然没有用户可访问的“真实”方式向 Microsoft 报告 VBA 错误。这一切都非常令人沮丧,因为我们有一些 VBA 项目由于内存需求迫切需要移植到 64 位。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-09-22
    • 1970-01-01
    • 1970-01-01
    • 2015-01-21
    • 2015-10-24
    • 1970-01-01
    • 2018-01-16
    • 2021-10-28
    相关资源
    最近更新 更多