【问题标题】:Integer Vs Long Confusion整数与长混淆
【发布时间】:2014-12-30 06:44:23
【问题描述】:

我看到很多人相信以下观点

VBA 将所有整数值转换为 Long 类型

事实上,连MSDN article 都说

“然而,在最近的版本中,VBA 将所有整数值转换为 Long 类型,即使它们被声明为 Integer 类型。”

这怎么可能?考虑这个简单的例子。

Sub Sample()
    Dim I As Integer
    I = 123456789
End Sub

如果VBA 将所有Integer 值转换为Long 类型即使它们被声明为Integer 类型,那么上面的内容绝不应该给您Overflow 错误! p>

我在这里缺少什么?还是我应该认为该声明不正确并认真注意链接开头所说的内容

【问题讨论】:

  • 我相信这是关于整数数据类型的实现细节。 VBA 可能会在内部将其存储为 Long,但它显然会强制执行原始的 min-max 范围。
  • 感谢@GSerg。您可能在这里有一个有效的观点。你能用可信的东西来支持这个说法吗?
  • 不,这是猜测。实际上,我发现链接文章中的陈述相当模糊且无益。显然,在某些情况下,整数将作为长整数传递或处理,但在大多数情况下,这似乎不会发生。如果我声明两个整数变量abVarPtr(a) - VarPtr(b) 给我2,很明显它总是2 用于数组。
  • 您链接到的页面似乎最初来自 Office2000 文档here。 Office95 是第一个完全 32 位的版本,所以如果作者指的是从 16 位到 32 位寄存器大小的变化,那是对“最近的版本......”的一个可怕的松散用法。其他措辞也将具有可怕的误导性 - VBA 类型在那种情况下转换,它只是变得更有效率的操作。
  • "for anyone who may still be using these technologies"? Wow - I feel alienated.

标签: vba


【解决方案1】:

声明为Integer 的整数仍被类型检查为Integer。 msdn 文档引用了变量在内部的存储方式。在 32 位系统上,整数将存储在 32 个 BITS 而不是 Bytes 中,而在 16 位系统上系统值存储在 16 个 BIT 空间或寄存器中,它本来应该存储在 16 中。因此是最大大小。

就 VBA 而言,没有进行类型转换。一个 int 是一个 int,一个 long 是一个 long,即使 they now take up just as much space

【讨论】:

  • 我知道 Integer 和 Long 是如何存储在内存中的。 There is no type conversion going on as far as VBA is concerned. 即使我相信 :) 只是 If VBA converts all Integer values to type Long even if they're declared as type Integer 的说法非常具有误导性,我认为可能有一些我不知道的事情。
  • 他们的意思可能是,如果您将一个值声明为 Integer,但在 OM/VBA 期望 Long 的地方使用它,则转换会自动完成。但除此之外,它似乎不是:“对于 x = 1 到 50000”如果 x 变暗为整数(实际上 x = 0)将产生溢出错误。但是只要 x
  • 就像其他答案一样,这不太可能是真的。 VBA Integer 似乎不占用 32 位,无论是在 32 位系统上还是在 64 位系统上。通过声明Integers 的数组并观察生成的内存布局,或者通过声明单个Integer 变量并检查它们的VarPtrs,很容易验证。
  • 我相信 MSDN 作者在解释他们的意思时做得很糟糕。鉴于可观察到的VarPtrs,为了使那篇文章成为真实,需要有一个完整的虚拟内存层专门用于隐藏Integer 变量的真实地址(现在需要 32 位),这将适用于包括互操作在内的所有场景.我不相信他们实际上实现了类似的东西,只是为了可以在内部将Integers 存储为Longs。
【解决方案2】:

我在 VBA 环境中工作了很多时间,并且完全有理由相信本文中的说法充其量只是误导。

我从未遇到过自动进行意外转换的情况。当然,将按值分配给更大的类型(例如DoubleLong)将是隐式的。

自动转换是一项重大更改的一个特定情况是分配给Variant 类型。如果没有转换,类型将为 VT_I2,转换为 VT_I4。

将整数类型 ByRef 传递给期望 Long 的函数会在 Office 2013 中发出类型不匹配。

我怀疑它们指的是 Integer 的内部存储:它们很可能没有在内存中的 16 位字上对齐(参见 C / C++ 中的 short 结构成员)。他们可能正在谈论这个。

【讨论】:

  • I've spent a lot of time working in the VBA environment... 我同意,但在我说我同意之前想确定一下。 :) I've never come across a situation where an automatic unexpected conversion is made. 这里也一样。 I suspect they are referring to the internal storage of the Integer... 可能但需要确定这就是他们的意思。将等待更多回复。
  • 打败我。是的。这篇文章是指内部存储大小。不过,如果您可以挖掘 16 位系统和 Office 版本,Integer 将仅以 16 位而不是 32 位存储。
  • The article is referencing the internal storage size. @RubberDuck:简单地说,您假设文章假设通过做出该声明,用户将知道它引用的是内部存储大小。 :D 在这个问题结束后,我计划向 MS 提供反馈,以改进或取消它。但是我怀疑他们会采取行动:)
  • 是的......整个事情都非常无证。我一直在尝试在我链接到的答案中收集尽可能多的信息。说到糟糕的文档......我昨天才找到并报告了this
  • 我意识到这是一篇旧帖子,但这里有一个意外转换的案例:MsgBox (2147483647) mod 3 将始终将分子转换为 LONG,即使您定义为双精度。示例设置n as double 然后n= 2147483647 并运行代码n mod 3 它将工作。然后更改 n = n+1 并进行 mod 计算 n mod 3 ,它会在内存上出错,因为 n 是 Long 并且不能超过其 21 亿的限制。
【解决方案3】:

“然而,在最近的版本中,VBA 将所有整数值转换为 Long 类型,即使它们被声明为 Integer 类型。”

我不相信那些文档。考虑以下简单示例(在 Excel 2010 中运行):

Sub checkIntegerVsLong()
    
    'Check the total memory allocation for an array
    Dim bits As Integer 'or long? :)
    Dim arrInteger() As Integer
    ReDim arrInteger(1 To 5)
    arrInteger(1) = 12
    arrInteger(2) = 456
    'get total memory allocation for integer in array
    bits = VarPtr(arrInteger(2)) - VarPtr(arrInteger(1))
    Debug.Print "For integer: " & bits & " bits and " & bits * 8 & " bytes."


    Dim arrLong() As Long
    ReDim arrLong(1 To 5)
    arrLong(1) = 12
    arrLong(2) = 456
    
    'get memory allocation for long
    bits = VarPtr(arrLong(2)) - VarPtr(arrLong(1))
    Debug.Print "For long: " & bits & " bits and " & bits * 8 & " bytes."

End Sub

打印出来:

对于整数:2 位和 16 字节。

长:4 位和 32 字节。

您还可以使用以下方法对单个变量进行测试:

Sub testIndividualValues()
    
    Dim j As Long
    Dim i As Integer
    Dim bits As Integer
    
    bits = LenB(i)
    Debug.Print "Length of integer: " & bits & " bits and " & bits * 8 & " bytes."
    bits = LenB(j)
    Debug.Print "Length of long: " & bits & " bits and " & bits * 8 & " bytes."

    
    
End Sub

打印出来的

整数长度:2位16字节。

long的长度:4位32字节。

最后,您可以在这里使用类型比较:

Public Type myIntegerType
    a As Integer
    b As Integer
End Type
Public Type myLongType
    a As Long
    b As Long
End Type

Public Sub testWithTypes()
    Dim testInt As myIntegerType
    Dim testLong As myLongType
    Dim bits As Integer
    
    bits = VarPtr(testInt.b) - VarPtr(testInt.a)
    Debug.Print "For integer in types: " & bits & " bits and " & bits * 8 & " bytes."
    
    bits = VarPtr(testLong.b) - VarPtr(testLong.a)
    Debug.Print "For long in types: " & bits & " bits and " & bits * 8 & " bytes."
    
End Sub

打印:

对于整数类型:2 位和 16 字节。

对于 long 类型:4 位和 32 字节。

这对我来说是非常有说服力的证据,表明 VBA 实际上确实以不同的方式对待 IntegerLong

如果 VBA 在后台静默转换,您会期望它们为每个指针分配位置返回相同数量的位/字节。但是在第一种情况下,对于 Integer,它只分配 16 位,而对于 Long 变量,它分配 32 位。

那又怎样?

所以你的问题

如果 VBA 将所有 Integer 值转换为 Long 类型,即使它们被声明为 Integer 类型,那么上述内容绝不应该给您溢出错误!

你会得到一个溢出错误是完全有道理的,因为 VBA 实际上并没有为 Long 分配内存给 Integer 声明。

如果这在所有版本的 Office 上返回相同,我也会很好奇。我只能在 64 位 Windows 7 上测试 Office 2010。

【讨论】:

  • 我认为您无法使用 VBA 代码测量 VBA 的内部存储...如果 Microsoft 修改 VBA 运行时以在内部使用 32 位整数,传播该修改对语言来说是一个意外的重大变化:我认为 VBA 代码无法证明 VBA 的内部结构。
  • @MathieuGuindon 有趣的想法。但是相同的 VBA 代码提供了有效的实际指针,用于与 CopyMemory API 或其他类似函数一起使用的实际内存位置; LenB 和 VarPtr 因此必须说实话。我相信对 MSDN 文章的正确解释是,对于内部操作(如加法和除法等),VBA 会将整数内的值复制到 long 的下半部分,进行计算并复制回整数(+- 溢出错误) .但就内存布局而言,整数确实占用 2 个字节,而 VBA 代码可以证明内部发生了什么
【解决方案4】:

转换仅用于内存优化,不适用于用户代码。对于程序员来说,几乎没有变化,因为数据类型的最小/最大限制保持不变。

如果你把那个段落作为一个整体,你会意识到这个陈述只是在表演的背景下,而不是在其他方面。这是因为数字的默认大小是 Int32 或 Int64(取决于它是 32 位还是 64 位系统)。处理器可以一次性处理这么大的数字。如果你声明一个比这个更小的单元,编译器必须缩小它,这比简单地使用默认类型需要更多的努力。处理器也确实没有任何收获。因此,即使您将变量声明为Integer,编译器也会为其分配Long 内存,因为它知道它必须做更多的工作而没有任何收获。

作为一名 VBA 程序员,对你来说意义重大的是 – Declare your variables as LONG instead of INTEGER even if you want to store small numbers in them.

【讨论】:

  • even though you declare your variable as Integer, the compiler allocates it a Long memory - 请解释为什么 VarPtr 仍然存在差异 2,以及分配 Integers 的数组时会发生什么。
【解决方案5】:

到目前为止,我还没有看到有人提到字节对齐的问题。为了操纵数字,需要将其加载到寄存器中,并且通常,一个寄存器不能包含多个变量。我认为寄存器也需要从上一条指令中清除,因此为确保变量正确加载需要重新对齐,这可能还涉及sign extending or zeroing out the register

您还可以使用 VBA 代码观察字节对齐:

Public Type x
    a As Integer
    b As Integer
    l As Long
End Type

Public Type y
    a As Integer
    l As Long
    b As Integer
End Type

Public Sub test()
    Dim x As x
    Dim y As y

    Debug.Print LenB(x)
    Debug.Print LenB(x.a), LenB(x.b), LenB(x.l)

    Debug.Print LenB(y)
    Debug.Print LenB(y.a), LenB(y.l), LenB(y.b)
End Sub

即使 UDT xy 包含相同数量的成员并且每个成员都是相同的数据类型;唯一的区别是成员的排序,LenB() 会给出不同的结果;在 32 位平台上,x 仅消耗 8 个字节,而y 将需要 12 个字节。 x.ax.l 之间以及 x.b 之后的高位字将被忽略。

另一点是问题不是 VBA 独有的。例如,C++ 具有与herehere 相同的注意事项。所以这实际上是低得多的级别,因此为什么在将变量加载到寄存器中以执行操作时,您无法“看到”符号扩展/零扩展行为。要看到这一点,您需要拆卸。

【讨论】:

    【解决方案6】:

    就我的测试而言,VBA 整数仍然需要两个字节(在 Access 2016 上测试,内部版本 8201)。

    据我所知,操作而不是存储会隐式转换为long(如果是写操作,则返回)。例如。如果我做myInt + 1myInt 被强制转换为 long,然后一个被添加到那个 long,然后结果被强制转换回 int,与仅使用 Long 相比导致性能损失。因此,虽然使用整数会消耗更少的内存,但所有操作的性能都会受到影响。

    正如 Mathieu Guindon 在 Enderland/Elysian Fields 的回答中指出的那样,使用 VBA 函数测试 VBA 的存储并不能证明任何事情,所以让我们更底层,直接查看内存中存储的内容,并操作该内存。

    首先,声明:

    Declare PtrSafe Sub CopyMemory Lib "Kernel32.dll" Alias "RtlMoveMemory" (ByVal Destination As LongPtr, ByVal Source As LongPtr, ByVal Length As Long)
    
    Public Function ToBits(b As Byte) As String
        Dim i As Integer
        For i = 7 To 0 Step -1
            ToBits = ToBits & IIf((b And 2 ^ i) = (2 ^ i), 1, 0)
        Next
    End Function
    

    现在,我要证明两件事:

    1. VarPtr 指向的内存包含 16 位整数
    2. 操作此内存会操作 VBA 使用的整数,即使您在 VBA 之外操作它也是如此

    代码:

    Dim i(0 To 1) As Integer
    'Using negatives to prove things aren't longs, because of the sign bit
    i(0) = -2 ^ 15 + (2 ^ 0) '10000000 00000001
    i(1) = -2 ^ 15 + (2 ^ 1) '10000000 00000010
    Dim bytes(0 To 3) As Byte
    CopyMemory VarPtr(bytes(0)), VarPtr(i(0)), 4
    Dim l As Long
    For l = 3 To 0 Step -1
        Debug.Print ToBits(bytes(l)) & " ";
        'Prints 10000000 00000010 10000000 00000001
    Next
    'Now, let's write something back
    bytes(0) = &HFF '0xFFFF = signed -1
    bytes(1) = &HFF
    CopyMemory VarPtr(i(0)), VarPtr(bytes(0)), 2
    Debug.Print i(0) '-1
    

    因此,当我们将事物声明为整数时,我们可以确定 VBA 确实将 2 字节整数写入内存,并从内存中读取它们。

    【讨论】:

      【解决方案7】:

      查看其他答案和 MSDN 文档,我认为“内部存储”一词不准确,这令人困惑。

      Tl;DR

      整数不像 Long 那样“存储在内部”,也就是说,它们不像 Long 那样需要相同数量的内存来保存它们的值。相反,它们作为 Long 被“内部使用”,这意味着它们的值在被复制回之前被访问(例如递增循环计数器)时临时存储在 Long 变量中,一般来说,整数数组将需要一半的内存作为一个 Long 数组。


      @enderland's answer表明Integers、Integer Arrays和由Integerssuch as a DWORD组成的UDT的内存布局都符合声明为整数的变量中包含的值占用2字节内存的想法。

      这是从 VBA 代码的角度来看的,这意味着可以假设

      1. VarPtrLenB 分别给出的内存位置和大小 are incorrect(谎言)在整数的情况下,以避免在从 16 位系统切换到 32 位系统时破坏现有代码
      2. some sort of abstraction layer,这意味着内存看起来是一回事,但实际上是另一回事

      我们可以排除这两种情况。

      可以使用 CopyMemory API 和 VarPtr 给出的地址和 LenB 给出的宽度直接覆盖数组中的值。 API 不受 VBA 的控制,它所做的只是直接将位写入内存。这完全可能的事实意味着VarPtr 必须指向内存中的一个区域,其中LenB 字节用于存储该整数的值;没有其他办法,2字节是用于编码整数值的空间量。

      抽象层仍然可能是真的; VBA 可以容纳一个 2 字节间隔的内存数组(SAFEARRAYS 都是连续的内存,这就是 CopyMemory 可以一次写入 2 个条目的原因),其中 VarPtr 指向。同时,一个单独的 4 字节间隔的内存块遮盖了 2 字节间隔的块,始终保持同步,以便整数可以存储为 Long。听起来很奇怪,但有可能发生吗?


      没有,我们可以通过查看任务管理器中的进程内存看到这一点:

      空闲,Excel 使用155,860KB 的内存(155,860 * 1024 字节)

      运行这个:

      Sub testLongs()
          Dim longs(500, 500, 500) As Long
          Stop
      End Sub
      

      ...它飙升至647,288KB。将差值除以数组元素的数量得到 ~4.03 个字节/Long。整数的相同测试:

      Sub t()
          Dim ints(500, 500, 500) As Integer
          Stop
      End Sub
      

      ...给出401,548KB,或~2.01 个字节/整数

      空闲内存使用量会略有不同,因此确切的数字无关紧要,但显然 Integer 数组正在使用 ~ Long 数组的一半内存


      所以我对MSDN文章的解读如下:

      在内存方面,整数实际上存储为 2 个字节的值,而不是 4 个字节的 Long。没有抽象或诡计的指针来向我们隐藏这一点。

      文章告诉我们,当整数用于运算(乘法/加法等)时,它们的值首先被复制到int32/VBA Long 的下半部分,计算发生在优化的 32-有点友好的方式,然后将结果复制回 Integer 并根据需要引发溢出错误。对于 Longs,不需要向前和向后复制(因此建议)。

      【讨论】:

        猜你喜欢
        • 2012-12-29
        • 1970-01-01
        • 2011-07-03
        • 2015-01-26
        • 2011-02-03
        • 2021-11-22
        • 1970-01-01
        • 1970-01-01
        • 2014-07-09
        相关资源
        最近更新 更多