VBA 在 If 语句中采用错误的分支 - 严重的编译器错误?

GWD*_*GWD 34 oop excel vba if-statement compiler-bug

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

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

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
Run Code Online (Sandbox Code Playgroud)

我们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
Run Code Online (Sandbox Code Playgroud)

代码是不言自明的。尽管适当命名的函数返回了第二个if语句,但无论输入如何!当类中的函数被类似的.FalseeFalsePublic 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
Run Code Online (Sandbox Code Playgroud)

(为简洁起见,省略了第一个if语句。)代码按预期执行,因此直接在if语句的条件中调用该函数至关重要(通常不会产生影响)。

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

Private Sub Class_Terminate()
End Sub
Run Code Online (Sandbox Code Playgroud)

如果语句按预期工作没有东西出来!所以不知何故,在评估 If 语句期间执行的 Terminate 事件把事情搞砸了,但Class_Terminate()sub 甚至不包含任何代码!这是下一件不应该有所作为的事情,但确实如此!

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

Public Function Falsee(oClass As cClass) As Boolean
    Set poClass = oClass
    Falsee = False
End Function
Run Code Online (Sandbox Code Playgroud)

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

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

模块代码:

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
Run Code Online (Sandbox Code Playgroud)

在类模块中:

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

Private Sub Class_Terminate()
End Sub
Run Code Online (Sandbox Code Playgroud)

总而言之,行为发生在:

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

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

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

Cri*_*use 15

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

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

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
Run Code Online (Sandbox Code Playgroud)

  • _注意:_虽然非解决方案_通常_不被接受为答案,但如果问题尚未得到解决,那么提供额外信息或进一步隔离或定义问题的部分答案是受欢迎的:“帮助我们找到一个解决方案。” “通过研究问题来找到解决方案,然后贡献你的研究结果以及你尝试过的任何其他内容作为部分答案。这样,即使我们无法弄清楚,下一个人也有更多的事情要做。” ([来源](https://stackoverflow.com/help/how-to-answer)) (8认同)