整数与长期混乱

时间:2014-11-03 15:14:47

标签: vba

我见过很多人相信以下

VBA将所有整数值转换为Long

类型

事实上,即使MSDN article

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

enter image description here

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

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

如果VBA将所有Integer值转换为Long 类型,即使它们已被声明为Integer类型,那么以上内容永远不会给您{ {1}}错误!

我在这里缺少什么?或者,我是否应该认为该陈述不正确,并认真注意该链接在开头说明

7 个答案:

答案 0 :(得分:9)

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

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

答案 1 :(得分:6)

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

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

自动转换将是突破性更改的一个特定情况是对Variant类型的分配。如果没有转换,则类型为VT_I2,转换为VT_I4。

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

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

答案 2 :(得分: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.

答案 3 :(得分: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字节。

     

长度: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字节。

     

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

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

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

那又怎样?

那么你的问题

  

如果VBA将所有Integer值转换为Long类型,即使它们被声明为Integer类型,那么上面的内容永远不会给你溢出错误!

完全可以理解你会遇到溢出错误,因为VBA实际上没有为Long Integer声明分配内存。

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

答案 4 :(得分:2)

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

据我所知,

操作而不是存储会隐式转换为较长的时间(如果是写操作,则回退)。例如。如果我这样做了myInt + 1,则myInt被强制转换为长整数,然后将其加到该整数,然后将结果强制转换为整数,与仅使用{ {1}}。因此,尽管使用整数花费较少的内存,但是所有操作都会降低性能。

正如Mathieu Guindon在Enderland / Elysian Fields的回答中所指出的那样,使用VBA功能测试VBA的存储无法证明任何事情,因此让我们更深入地研究并直接查看存储在内存中的内容,并操纵该内存。

首先,声明:

Long

现在,我要证明两件事:

  1. 内存VarPtr指向包含16位整数
  2. 操纵此内存可以操纵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

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

答案 5 :(得分:1)

到目前为止,我还没有看到有人提到字节对齐问题。为了处理数字,需要将其加载到寄存器中,并且通常,一个寄存器不能包含多个变量。我认为还需要从上一条指令中清除寄存器,因此要确保正确加载变量,就需要重新对齐,这可能还涉及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 :(得分:0)

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

Tl; DR

整数不像Longs那样“内部存储”,也就是说,它们不需要像Longs那样占用相同数量的内存来保存其值。而是将它们“内部”用作Longs,这意味着它们的值在被复制回副本之前每次被访问(例如,增加循环计数器)时都会暂时存储在Long变量中,并且通常来说,一个Integer数组将需要一半的内存作为一组Longs。


@enderland's answer表明,由整数such as a DWORD组成的Integer,Integer Array和UDT的内存布局均符合这样的想法:声明为整数的变量中包含的值占用2个字节的内存

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

  1. 在整数的情况下,VarPtrLenB(分别为are incorrect(谎言)给出的存储位置和大小避免了在从16位系统切换到32位系统时采用的方式破坏现有代码放置
  2. some sort of abstraction layer,这意味着记忆是一回事,但实际上是另一回事。

我们可以排除这两个方面。

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

尽管如此,抽象层仍然可以成立。 VBA可以容纳一个数组,每个数组2个字节的内存(SAFEARRAYS都是连续的内存,这就是为什么CopyMemory可以一次写入2个条目),VarPtr指向该数组。同时,一个单独的4字节间隔的内存块遮盖了2字节间隔的块,并始终保持同步,因此整数可以存储为Longs。听起来很奇怪,但可能会发生吗?


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

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

运行此:

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

...并且峰值达到647,288KB。将差值除以数组元素的数量可得到〜4.03字节/长。整数的相同测试:

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

...给出401,548,或每个整数〜2.01字节

空闲内存使用情况会略有变化,因此确切的数字并不重要,但是很明显,Integer数组使用的是Long数组的〜内存的一半


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

在内存方面,整数实际上存储为2个字节的值,而不是4个字节的Longs。没有指针的抽象或欺骗手段可以将其隐藏起来。

相反,该文章告诉我们,当在运算(乘法/加法等)中使用整数时,会将其值首先复制到int32 / VBA Long的下半部分,则在一种优化的32位友好方式,然后将结果复制回Integer,并根据需要引发溢出错误。对于Longs,不需要前后复制(因此推荐)。