vba - type - miembros de integer
Entero vs larga confusiĆ³n (7)
"En versiones recientes, sin embargo, VBA convierte todos los valores enteros a tipo Long, incluso si se declaran como tipo Integer".
No creo en esa documentación. Considere el siguiente ejemplo simple (ejecutado en 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
Esto imprime:
Para entero: 2 bits y 16 bytes.
Para largo: 4 bits y 32 bytes.
También puede probar esto en variables individuales usando lo siguiente:
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
que imprime
Longitud del entero: 2 bits y 16 bytes.
Longitud de largo: 4 bits y 32 bytes.
Por último, puede usar una comparación de tipos aquí:
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
que también imprime:
Para enteros en tipos: 2 bits y 16 bytes.
Para largos en tipos: 4 bits y 32 bytes.
Esta es una evidencia bastante convincente para mí de que VBA realmente trata a
Integer
y
Long
diferente.
Si VBA convierte silenciosamente detrás de escena, es de esperar que devuelvan el mismo número de bits / bytes para cada una de las ubicaciones de asignación de puntero. Pero en el primer caso, con Integer, solo asigna 16 bits, mientras que para las variables largas, asigna 32 bits.
¿Y qué?
Entonces a su pregunta de
Si VBA convierte todos los valores Integer en tipo Long, incluso si se declaran como tipo Integer, ¡lo anterior nunca debería dar el error de desbordamiento!
Tiene mucho sentido que obtenga un error de desbordamiento, ya que VBA no ha asignado realmente la memoria durante una declaración
Long
al
Integer
.
También me gustaría saber si esto devuelve lo mismo en todas las versiones de Office. Solo puedo probar en Office 2010 en Windows 7 de 64 bits.
He visto a muchos creer en lo siguiente
VBA convierte todos los valores enteros para escribir Long
De hecho, incluso el artículo de MSDN dice
"En versiones recientes, sin embargo, VBA convierte todos los valores enteros a tipo Long, incluso si se declaran como tipo Integer".
¿Cómo es esto posible? Considere este simple ejemplo.
Sub Sample()
Dim I As Integer
I = 123456789
End Sub
Si
VBA
convierte todos los valores de
Integer
en tipo
Long
incluso si se declaran como tipo Integer
, ¡lo anterior nunca debería dar el error de
Overflow
!
¿Que me estoy perdiendo aqui? ¿O debería considerar que la declaración es incorrecta y prestar atención a lo que dice el enlace al principio
En lo que respecta a mis pruebas, un entero VBA todavía toma dos bytes (Probado en Access 2016, compilación 8201).
Por lo que puedo encontrar, la conversión implícita a un largo (y viceversa, si es una operación de escritura) ocurre para
operaciones
, no para
almacenamiento
.
Por ejemplo, si hago
myInt + 1
,
myInt
se lanza a un largo, luego se agrega uno a ese largo, y luego el resultado se devuelve a un int, lo que resulta en una pérdida de rendimiento en comparación con solo usar un
Long
.
Entonces, aunque cuesta menos memoria usar un número entero, todas las operaciones sufrirán un rendimiento.
Como Mathieu Guindon señaló en la respuesta de Enderland / Elysian Fields, probar el almacenamiento de VBA con funciones de VBA no puede probar nada, así que vamos a un nivel más bajo y veamos directamente lo que está almacenado en la memoria, y manipulemos esa memoria.
Primero, declaraciones:
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
Ahora, voy a demostrar dos cosas:
- La memoria a la que apunta VarPtr contiene enteros de 16 bits
- Manipular esta memoria manipula los enteros que usa VBA, incluso si lo manipula fuera de VBA
El código:
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
Entonces, podemos estar seguros de que VBA de hecho escribe números enteros de 2 bytes en la memoria y los lee de nuevo desde la memoria, cuando declaramos las cosas como un número entero.
Hasta ahora, no he visto a nadie mencionar el problema de la alineación de bytes. Para manipular los números, debe cargarse en el registro y, como regla, un registro no puede contener más de una variable. Creo que los registros también deben borrarse de la instrucción anterior, por lo que para garantizar que la variable se cargue correctamente, debe realinearse, lo que también puede implicar que el signo se extienda o ponga a cero el registro .
También puede observar la alineación de bytes usando el código 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
Aunque el UDT
x
e
y
contiene el mismo número de miembros y cada miembro tiene el mismo tipo de datos;
con la única diferencia en el orden de los miembros,
LenB()
dará resultados diferentes;
En una plataforma de 32 bits,
x
consume solo 8 bytes, mientras que
y
necesitará 12 bytes.
La palabra alta entre
xa
y
xl
y después de
xb
simplemente se ignora.
El otro punto es que el problema no es exclusivo de VBA. Por ejemplo, C ++ tiene las mismas consideraciones que se ilustran here y here . Entonces, este es en realidad un nivel mucho más bajo y, por lo tanto, no puede "ver" el comportamiento de extensión de signo / extensión de cero al cargar las variables en registros para realizar la operación. Para ver eso, necesitas el desmontaje.
He pasado mucho tiempo trabajando en el entorno de VBA y tengo todas las razones para creer que la afirmación de este artículo es, en el mejor de los casos, engañosa.
Nunca me he encontrado con una situación en la que se realiza una conversión automática inesperada.
Por supuesto, la asignación
por valor
a un tipo más grande (como
Double
o
Long
) estaría implícita.
Un caso específico donde la conversión automática sería un cambio radical sería una asignación a un tipo de
Variant
.
Sin una conversión, el tipo sería VT_I2, con la conversión VT_I4.
Al pasar un tipo entero
ByRef
a una función que espera un
Long
emite un tipo no coincidente en Office 2013.
Sospecho que se están refiriendo al almacenamiento interno del número
Integer
: es muy probable que no estén alineados con palabras de 16 bits en la memoria (cf. un miembro de estructura
short
en C / C ++).
Probablemente estén hablando de eso.
La conversión es solo para la optimización de la memoria, no para el código de usuario. Para el programador, prácticamente no hay cambios ya que los límites mínimo / máximo de los tipos de datos siguen siendo los mismos.
Si toma ese parámetro como un todo, se dará cuenta de que esa declaración es solo en el contexto del rendimiento, y no de otra manera.
Esto se debe a que el tamaño predeterminado de los números es Int32 o Int64 (dependiendo de si es un sistema de 32 o 64 bits).
El procesador puede procesar hasta ese gran número de una vez.
Si declara una unidad más pequeña que esta, el compilador tiene que reducir su tamaño, y eso requiere más esfuerzos que simplemente usar el tipo predeterminado.
Y el procesador tampoco tiene ganancia.
Entonces, aunque declare su variable como
Integer
, el compilador le asigna una memoria
Long
, porque sabe que tiene que hacer más trabajo sin ninguna ganancia.
Como programador de VBA, lo que es importante para usted es:
Declare your variables as LONG instead of INTEGER even if you want to store small numbers in them.
Mirando las otras respuestas y la documentación de MSDN, creo que la frase "almacenado internamente" es imprecisa y eso es lo que es confuso.
Tl; DR
Los enteros no se "almacenan internamente" como Longs, es decir, no requieren la misma cantidad de memoria para guardar sus valores que un Long. Por el contrario, se "usan internamente" como Longs, lo que significa que su valor se almacena temporalmente en una variable Long cada vez que se accede (por ejemplo, incrementando un contador de bucle) antes de ser copiado, y en general, una matriz de Integers requerirá la mitad de la memoria como una serie de Longs.
La respuesta de @enderland muestra que el diseño de memoria de enteros, matrices de enteros y UDT compuestos por enteros como DWORD se ajusta a la idea de que el valor contenido en una variable declarada como un entero ocupa 2 bytes de memoria.
Esto es desde el punto de vista del código VBA, lo que significa que es posible suponer que
-
Las ubicaciones y tamaños de memoria proporcionados por
VarPtr
yLenB
respectivamente son incorrectos (mentiras) en el caso de Integers para evitar romper el código existente cuando se realizó el cambio de los sistemas de 16 a 32 bits. - Hay algún tipo de capa de abstracción que significa que la memoria aparece como una cosa pero en realidad es otra
Podemos descartar estos dos.
Es posible utilizar la API CopyMemory con una dirección dada por VarPtr y un ancho dado por LenB para sobrescribir valores en una matriz directamente.
La API no está bajo el control de VBA, y todo lo que hace es escribir bits directamente en la memoria.
El hecho de que esto sea posible significa que
VarPtr
debe apuntar a un área en la memoria donde se
LenB
bytes
LenB
para almacenar el valor de ese entero;
de otra manera, 2 bytes es la cantidad de espacio utilizado para codificar el valor de un entero.
Sin embargo, la capa de abstracción aún podría ser cierta; VBA podría contener una matriz de memoria espaciada de 2 bytes (SAFEARRAYS son todas memorias consecutivas, es por eso que CopyMemory puede escribir 2 entradas a la vez) a donde apunta VarPtr. Mientras tanto, un bloque separado de memoria de 4 bytes espacia la sombra del bloque de 2 bytes, manteniéndose constantemente sincronizado para que los números enteros se puedan almacenar como Longs. Suena raro pero podría pasar ¿verdad?
No lo hace, y podemos ver esto mirando la memoria del proceso en el Administrador de tareas:
Inactivo, Excel usa
155,860KB
de memoria (155,860 * 1024 bytes)
Ejecuta esto:
Sub testLongs()
Dim longs(500, 500, 500) As Long
Stop
End Sub
... y
647,288KB
.
Tomar la diferencia y dividir por el número de elementos de la matriz da
~ 4.03 bytes por Long
.
La misma prueba para enteros:
Sub t()
Dim ints(500, 500, 500) As Integer
Stop
End Sub
... da
401,548
, o
~ 2.01 bytes por entero
Habrá una ligera variación en el uso de la memoria inactiva, por lo que los números exactos no importan, pero claramente la matriz Integer está utilizando ~ la mitad de la memoria de la matriz Long
Entonces, mi interpretación del artículo de MSDN es la siguiente:
En cuanto a la memoria, los enteros se almacenan realmente como valores de 2 bytes, no como longitudes de 4 bytes. No hay abstracción o truco con punteros para ocultar esto de nosotros.
Más bien, el artículo nos dice que cuando se usan números enteros en operaciones (multiplicación / suma, etc.) sus valores se copian a la mitad inferior de un
int32
/
VBA Long
, el cálculo se realiza de una manera optimizada y amigable de 32 bits, y luego el resultado se copia de nuevo a Integer y se generan errores de desbordamiento según sea necesario.
Para Longs no hay necesidad de copiar hacia adelante y hacia atrás (de ahí la recomendación).
Un número entero declarado como un número
Integer
todavía se marca como un número
Integer
.
La documentación de msdn hace referencia a cómo se almacena internamente la variable.
En un sistema de 32 bits, un entero se almacenará en 32
BITS
no en
bytes
, mientras que en un sistema de 16 bits el valor se almacena en un
espacio o registro de
16
BIT
, se habría almacenado en 16. De ahí el tamaño máximo.
No hay conversión de tipo en lo que respecta a VBA. Un int es un int y un long es un long, a pesar de que ahora ocupan la misma cantidad de espacio .