¿Por qué los tipos siempre tienen un cierto tamaño sin importar su valor?


Las implementaciones pueden diferir entre los tamaños reales de los tipos, pero en la mayoría, los tipos como unsigned int y float son siempre de 4 bytes. Pero ¿por qué un tipo siempre ocupa una cierta cantidad de memoria sin importar su valor? Por ejemplo, si he creado el siguiente entero con el valor de 255

int myInt = 255;

Entonces myInt ocuparía 4 bytes con mi compilador. Sin embargo, el valor real, 255 se puede representar con solo 1 byte, entonces, ¿por qué myInt no solo ocuparía 1 byte de memoria? O la forma más generalizada de preguntar: ¿Por qué un tipo tiene solo un tamaño asociado cuando el espacio requerido para representar el valor puede ser menor que ese tamaño?

 148
c++
Author: Nichlas Uden, 2018-06-12

19 answers

Se supone que el compilador produce ensamblador (y en última instancia código máquina) para alguna máquina, y generalmente C++ intenta simpatizar con esa máquina.

Ser comprensivo con la máquina subyacente significa aproximadamente: facilitar la escritura de código C++ que se mapeará eficientemente en las operaciones que la máquina puede ejecutar rápidamente. Por lo tanto, queremos proporcionar acceso a los tipos de datos y operaciones que son rápidos y "naturales" en nuestra plataforma de hardware.

Concretamente, considere un arquitectura específica de la máquina. Tomemos la actual familia Intel x86.

El Manual para Desarrolladores de Software de Arquitecturas Intel® 64 e IA-32 vol 1 ( link ), sección 3.4.1 dice:

Los registros de propósito general de 32 bits EAX, EBX, ECX, EDX, ESI, EDI, EBP y ESP se proporcionan para la celebración de la elementos siguientes:

* Operandos para operaciones lógicas y aritméticas

* Operandos para cálculos de direcciones

• Memoria punteros

Por lo tanto, queremos que el compilador utilice estos EAX, EBX, etc. se registra cuando compila aritmética simple de enteros de C++. Esto significa que cuando declaro un int, debe ser algo compatible con estos registros, para que pueda usarlos eficientemente.

Los registros son siempre del mismo tamaño (aquí, 32 bits), por lo que mis variables int siempre serán de 32 bits también. Usaré el mismo diseño (little-endian) para no tener que hacer una conversión cada vez que cargue un valor variable en un registro, o almacenar un registro de nuevo en una variable.

Usando godbolt podemos ver exactamente lo que hace el compilador para algún código trivial:

int square(int num) {
    return num * num;
}

Compila (con GCC 8.1 y -fomit-frame-pointer -O3 para simplificar) a:

square(int):
  imul edi, edi
  mov eax, edi
  ret

Esto significa:

  1. el parámetro int num se pasó en register EDI, lo que significa que es exactamente el tamaño y el diseño que Intel espera para un registro nativo. La función no tiene que convertir nada
  2. el la multiplicación es una sola instrucción (imul), que es muy rápida
  3. devolver el resultado es simplemente una cuestión de copiarlo a otro registro (la persona que llama espera que el resultado se ponga en EAX)

Editar: podemos agregar una comparación relevante para mostrar la diferencia que hace un diseño no nativo. El caso más simple es almacenar valores en algo que no sea ancho nativo.

Usando godbolt de nuevo, podemos comparar un nativo simple multiplicación

unsigned mult (unsigned x, unsigned y)
{
    return x*y;
}

mult(unsigned int, unsigned int):
  mov eax, edi
  imul eax, esi
  ret

Con el código equivalente para una anchura no estándar

struct pair {
    unsigned x : 31;
    unsigned y : 31;
};

unsigned mult (pair p)
{
    return p.x*p.y;
}

mult(pair):
  mov eax, edi
  shr rdi, 32
  and eax, 2147483647
  and edi, 2147483647
  imul eax, edi
  ret

Todas las instrucciones adicionales se refieren a la conversión del formato de entrada (dos enteros sin signo de 31 bits) al formato que el procesador puede manejar de forma nativa. Si quisiéramos almacenar el resultado de nuevo en un valor de 31 bits, habría otra o dos instrucciones para hacer esto.

Esta complejidad adicional significa que solo se molestaría con esto cuando el ahorro de espacio es muy importante. En este caso solo estamos guardando dos bits en comparación con el uso del tipo nativo unsigned o uint32_t, que habría generado código mucho más simple.


Una nota sobre los tamaños dinámicos:

El ejemplo anterior sigue siendo valores de ancho fijo en lugar de ancho variable, pero el ancho (y la alineación) ya no coinciden con los registros nativos.

La plataforma x86 tiene varios tamaños nativos, incluidos 8 bits y 16 bits, además del principal de 32 bits (estoy pasando por alto el modo de 64 bits y varias otras cosas por simplicidad).

Estos tipos (char, int8_t, uint8_t, int16_t etc.) son también soportados directamente por la arquitectura - en parte por la compatibilidad hacia atrás con 8086/286/386/etc. sucesivamente. juegos de instrucciones.

Ciertamente es el caso de que elegir el tipo natural de tamaño fijo más pequeño que será suficiente, puede ser una buena práctica : todavía son cargas y almacenes de instrucciones simples y rápidas, aún obtienes aritmética nativa a toda velocidad e incluso puedes mejorar rendimiento mediante la reducción de errores de caché.

Esto es muy diferente a la codificación de longitud variable-he trabajado con algunos de estos, y son horribles. Cada carga se convierte en un bucle en lugar de una sola instrucción. Cada tienda es también un bucle. Cada estructura es de longitud variable, por lo que no puede usar matrices de forma natural.


Otra nota sobre la eficiencia

En comentarios posteriores, has estado usando la palabra "eficiente", por lo que puedo decir con respecto al tamaño de almacenamiento. Lo hacemos a veces, elija minimizar el tamaño del almacenamiento: puede ser importante cuando guardamos un gran número de valores en archivos o los enviamos a través de una red. La compensación es que necesitamos cargar esos valores en registros para hacer cualquier cosa con ellos, y realizar la conversión no es gratis.

Cuando discutimos la eficiencia, necesitamos saber qué estamos optimizando y cuáles son las compensaciones. El uso de tipos de almacenamiento no nativos es una forma de negociar la velocidad de procesamiento por espacio, y a veces tiene sentido. Usando almacenamiento de longitud variable (al menos para tipos aritméticos), negocia más velocidad de procesamiento (y complejidad de código y tiempo de desarrollador) para un ahorro de espacio a menudo mínimo.

La penalización de velocidad que pagas por esto significa que solo vale la pena cuando necesitas minimizar absolutamente el ancho de banda o el almacenamiento a largo plazo, y para esos casos generalmente es más fácil usar un formato simple y natural , y luego comprimirlo con un sistema de propósito general (como zip, gzip, bzip2, xy o lo que sea).


Tl; dr

Cada plataforma tiene una arquitectura, pero puede llegar a un número esencialmente ilimitado de diferentes formas de representar datos. No es razonable que ningún lenguaje proporcione un número ilimitado de tipos de datos integrados. Por lo tanto, C++ proporciona acceso implícito al conjunto natural de tipos de datos nativos de la plataforma y le permite codificar cualquier otra representación (no nativa) usted mismo.

 131
Author: Useless,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-06-14 16:35:25

Porque los tipos representan fundamentalmente el almacenamiento, y se definen en términos de valor máximo que pueden mantener, no el valor actual.

La analogía muy simple sería una casa - una casa tiene un tamaño fijo, independientemente de cuántas personas vivan en ella, y también hay un código de construcción que estipula el número máximo de personas que pueden vivir en una casa de un cierto tamaño.

Sin embargo, incluso si una sola persona vive en una casa que puede acomodar a 10, el tamaño de la casa no se verá afectada por el número actual de ocupantes.

 140
Author: SergeyA,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-06-12 14:20:01

Es una optimización y simplificación.

Puede tener objetos de tamaño fijo. Almacenando así el valor.
O puede tener objetos de tamaño variable. Pero almacenando valor y tamaño.

Objetos de tamaño fijo

El código que manipula el número no necesita preocuparse por el tamaño. Asumes que siempre usas 4 bytes y haces que el código sea muy simple.

Objetos de tamaño dinámico

El código que el número manipula debe entender al leer una variable que debe leer el valor y el tamaño. Utilice el tamaño para asegurarse de que todos los bits altos son cero en el registro.

Cuando vuelva a colocar el valor en memoria si el valor no ha excedido su tamaño actual, simplemente vuelva a colocar el valor en memoria. Pero si el valor se ha reducido o crecido, debe mover la ubicación de almacenamiento del objeto a otra ubicación en la memoria para asegurarse de que no se desborde. Ahora tienes que rastrear la posición de ese número (ya que puede moverse si crece demasiado grande para su tamaño). También debe realizar un seguimiento de todas las ubicaciones variables no utilizadas para que puedan reutilizarse potencialmente.

Resumen

El código generado para objetos de tamaño fijo es mucho más simple.

Nota

La compresión utiliza el hecho de que 255 cabrá en un byte. Hay esquemas de compresión para almacenar grandes conjuntos de datos que utilizarán activamente diferentes valores de tamaño para diferentes números. Pero como no se trata de datos en vivo, no tiene las complejidades descritas anteriormente. Usted usa menos espacio para almacenar los datos a un costo de comprimir / descomprimir los datos para su almacenamiento.

 45
Author: Martin York,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-06-12 19:53:38

Porque en un lenguaje como C++, un objetivo de diseño es que las operaciones simples se compilen en simples instrucciones de máquina.

Todos los conjuntos de instrucciones principales de la CPU funcionan con tipos de ancho fijo, y si desea hacer tipos de ancho variable, debe hacer varias instrucciones de máquina para manejarlos.

En cuanto a por qué el hardware subyacente es de esa manera: Es porque es más simple y más eficiente para muchos casos (pero no todo).

Imagina la computadora como un pedazo de cinta:{[12]]}

| xx | xx | xx | xx | xx | xx | xx | xx | xx | xx | xx | xx | xx | ...

Si simplemente le dices a la computadora que mire el primer byte en la cinta, xx, ¿cómo sabe si el tipo se detiene allí o continúa hacia el siguiente byte? Si usted tiene un número como 255 (hexadecimal FF) o un número como 65535 (hexadecimal FFFF) el primer byte es siempre FF.

Entonces, ¿cómo lo sabes? Tienes que añadir lógica adicional ,y "sobrecargar" el significado de al menos un bit o byte valor para indicar que el valor continúa hasta el siguiente byte. Esa lógica nunca es "libre", ya sea que la emule en el software o agregue un montón de transistores adicionales a la CPU para hacerlo.

Los tipos de lenguajes de ancho fijo como C y C++ reflejan eso.

No tiene que ser de esta manera, y los lenguajes más abstractos que están menos preocupados con la asignación de código de máxima eficiencia son libres de usar codificaciones de ancho variable (también conocidas como " Longitud variable Cantidades " o VLQ) para tipos numéricos.

Lectura adicional: Si busca "cantidad de longitud variable" puede encontrar algunos ejemplos de dónde ese tipo de codificación es realmente eficiente y vale la pena la lógica adicional. Por lo general, es cuando necesita almacenar una gran cantidad de valores que pueden estar en cualquier lugar dentro de un rango grande, pero la mayoría de los valores tienden hacia algún pequeño sub-rango.


Tenga en cuenta que si un compilador puede probar que puede salirse con la suya valor en una menor cantidad de espacio sin romper ningún código (por ejemplo, es una variable solo visible internamente dentro de una sola unidad de traducción), y su heurística de optimización sugiere que será más eficiente en el hardware de destino, está completamente permitido optimizarlo en consecuencia y almacenarlo en una menor cantidad de espacio, siempre y cuando el resto del código funcione "como si" hiciera lo estándar.

Pero, cuando el código inter-operate con otro código que podría ser compilado por separado, los tamaños tienen que permanecer consistentes, o asegurarse de que cada pieza de código siga la misma convención.

Porque si no es consistente, hay esta complicación: ¿Qué pasa si tengo int x = 255; pero luego en el código lo hago x = y? Si int pudiera ser de ancho variable, el compilador tendría que saber de antemano para pre-asignar la cantidad máxima de espacio que necesitará. Eso no siempre es posible, porque ¿qué pasa si y es un argumento pasado de otra pieza de código que se compila por separado?

 27
Author: mtraceur,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-06-12 18:27:30

Java utiliza clases llamadas "BigInteger" y "BigDecimal" para hacer exactamente esto, al igual que la interfaz de clase C++GMP de C++ aparentemente (gracias a Digital Trauma). Puedes hacerlo fácilmente en casi cualquier idioma si lo deseas.

Las CPU siempre han tenido la capacidad de usar BCD (Decimal Codificado Binario) que está diseñado para soportar operaciones de cualquier longitud (pero tiende a operar manualmente en un byte a la vez, lo que sería LENTO para los estándares de GPU actuales.)

La razón por la que no utilice estas u otras soluciones similares? Rendimiento. Sus lenguajes de mayor rendimiento no pueden permitirse ir expandiendo una variable en medio de una operación de bucle apretado it sería muy no determinista.

En situaciones de almacenamiento masivo y transporte, los valores empaquetados son a menudo el ÚNICO tipo de valor que usaría. Por ejemplo, un paquete de música/video que se transmite a su computadora puede gastar un poco para especificar si el siguiente valor es de 2 bytes o 4 bytes como una optimización de tamaño.

Una vez que está en su computadora donde se puede usar, la memoria es barata, pero la velocidad y la complicación de las variables redimensionables no lo es.. esa es realmente la única razón.

 26
Author: Bill K,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-06-15 16:09:20

Porque sería muy complicado y pesado computar tener tipos simples con tamaños dinámicos. No estoy seguro de que esto sea posible.
La computadora tendría que comprobar cuántos bits toma el número después de cada cambio de su valor. Sería un montón de operaciones adicionales. Y sería mucho más difícil realizar cálculos cuando usted no sabe tamaños de variables durante la compilación.

Para soportar tamaños dinámicos de variables, la computadora tendría que recuerde cuántos bytes tiene una variable ahora mismo cuál ... requeriría memoria adicional para almacenar esa información. Y esta información tendría que ser analizada antes de cada operación en la variable para elegir la instrucción correcta del procesador.

Para comprender mejor cómo funciona la computadora y por qué las variables tienen tamaños constantes, aprenda los conceptos básicos del lenguaje ensamblador.

Aunque, supongo que sería posible lograr algo así con los valores constexpr. Sin embargo, esto sería hacer que el código sea menos predecible para un programador. Supongo que algunas optimizaciones de compiladores pueden hacer algo así, pero lo esconden de un programador para mantener las cosas simples.

He descrito aquí solo los problemas que afectan al rendimiento de un programa. He omitido todos los problemas que tendrían que ser resueltos para ahorrar memoria mediante la reducción de tamaños de variables. Honestamente, no creo que sea posible.


En conclusión, usar variables más pequeñas que las declaradas solo tiene sentido si sus valores son conocidos durante la compilación. Es muy probable que los compiladores modernos hagan eso. En otros casos causaría demasiados problemas difíciles o incluso irresolubles.

 20
Author: NO_NAME,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-06-12 14:50:55

Entonces myInt ocuparía 4 bytes con mi compilador. Sin embargo, el valor real, 255 se puede representar con solo 1 byte, entonces, ¿por qué myInt no solo ocuparía 1 byte de memoria?

Esto se conoce como codificación de longitud variable, hay varias codificaciones definidas, por ejemplo VLQ. Uno de los más famosos, sin embargo, es probablemente UTF-8: UTF-8 codifica puntos de código en un número variable de bytes, de 1 a 4.

O más forma generalizada de preguntar: ¿Por qué un tipo tiene solo un tamaño asociado cuando el espacio requerido para representar el valor puede ser menor que ese tamaño?

Como siempre en ingeniería, todo se trata de compensaciones. No hay una solución que solo tenga ventajas, por lo que debe equilibrar las ventajas y las compensaciones al diseñar su solución.

El diseño en el que se estableció fue usar tipos fundamentales de tamaño fijo, y el hardware / lenguajes simplemente volaron desde alli.

Entonces, ¿cuál es la debilidad fundamental de la codificación de variables, que causó que fuera rechazada en favor de esquemas más hambrientos de memoria? No hay Direcciones aleatorias.

¿Cuál es el índice del byte en el que comienza el 4to punto de código en una cadena UTF-8?

Depende de los valores de los puntos de código anteriores, se requiere un escaneo lineal.

Seguramente hay esquemas de codificación de longitud variable que son mejores en ¿direcciones aleatorias?

Sí, pero también son más complicados. Si hay uno ideal, nunca lo he visto todavía.

¿El direccionamiento aleatorio realmente importa de todos modos?

Oh SÍ!

La cosa es, cualquier tipo de agregado / matriz se basa en tipos de tamaño fijo:

  • Accediendo al 3er campo de a struct? ¡Direccionamiento aleatorio!
  • ¿Accediendo al 3er elemento de un array? ¡Direccionamiento aleatorio!

Lo que significa que esencialmente tiene la siguiente compensación:

Tipos de tamaño fijo O escaneos de memoria lineal

 16
Author: Matthieu M.,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-06-13 07:31:50

La memoria de la computadora se subdivide en trozos con direcciones consecutivas de un cierto tamaño (a menudo 8 bits, y denominados bytes), y la mayoría de las computadoras están diseñadas para acceder de manera eficiente a secuencias de bytes que tienen direcciones consecutivas.

Si la dirección de un objeto nunca cambia dentro de la vida útil del objeto, entonces el código dado a su dirección puede acceder rápidamente al objeto en cuestión. Una limitación esencial con este enfoque, sin embargo, es que si se asigna una dirección para la dirección X, y luego se asigna otra dirección para la dirección Y que está a N bytes de distancia, entonces X no podrá crecer más que N bytes dentro de la vida útil de Y, a menos que se mueva X o Y. Para que X se mueva, sería necesario que todo en el universo que contiene la dirección de X se actualice para reflejar la nueva, y del mismo modo para que Y se mueva. Si bien es posible diseñar un sistema para facilitar dichas actualizaciones (tanto Java como. NET lo gestionan bastante bien) es mucho más eficiente trabajar con él objetos que permanecerán en la misma ubicación durante toda su vida, lo que a su vez generalmente requiere que su tamaño permanezca constante.

 16
Author: supercat,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-06-13 21:05:12

La respuesta corta es: Porque el estándar C++ lo dice.

La respuesta larga es: Lo que se puede hacer en una computadora está limitado en última instancia por el hardware. Es, por supuesto, posible codificar un entero en un número variable de bytes para el almacenamiento, pero luego leerlo requeriría instrucciones especiales de la CPU para ser eficaz, o podría implementarlo en software, pero entonces sería terriblemente lento. Las operaciones de tamaño fijo están disponibles en la CPU para cargar valores de anchuras predefinidas, no hay ninguno para anchos variables.

Otro punto a considerar es cómo funciona la memoria de la computadora. Digamos que su tipo entero podría ocupar en cualquier lugar entre 1 a 4 bytes de almacenamiento. Supongamos que almacena el valor 42 en su entero: ocupa 1 byte, y lo coloca en la dirección de memoria X. Luego almacena su siguiente variable en la ubicación X + 1 (no estoy considerando la alineación en este punto) y así sucesivamente. Más tarde decide cambiar su valor a 6424.

Pero esto no encaja en un solo byte! Entonces, ¿a qué te dedicas? ¿Dónde pones el resto? Ya tienes algo en X + 1, así que no puedes colocarlo ahí. En otro lugar? ¿Cómo sabrás más tarde dónde? La memoria de la computadora no es compatible con la semántica de inserción: no se puede simplemente colocar algo en un lugar y empujar todo después de él a un lado para hacer espacio!

Aparte: Lo que estás hablando es realmente el área de compresión de datos. Existen algoritmos de compresión para empaquetar todo más apretado, por lo que al menos algunos de ellos considerarán no usar más espacio para su entero de lo que necesita. Sin embargo, los datos comprimidos no son fáciles de modificar (si es posible) y solo terminan siendo recomprimidos cada vez que realiza algún cambio en ellos.

 13
Author: John Doe the Righteous,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-06-12 20:55:57

Hay beneficios de rendimiento en tiempo de ejecución bastante sustanciales al hacer esto. Si tuviera que operar en tipos de tamaño variable, tendría que decodificar cada número antes de hacer la operación (las instrucciones del código máquina suelen ser de ancho fijo), hacer la operación y luego encontrar un espacio en la memoria lo suficientemente grande como para contener el resultado. Son operaciones muy difíciles. Es mucho más fácil simplemente almacenar todos los datos de manera ligeramente ineficiente.

No siempre es así como se hace. Considerar Protocolo Protobuf de Google. Los protobufs están diseñados para transmitir datos de manera muy eficiente. Disminuir el número de bytes transmitidos vale la pena el costo de instrucciones adicionales cuando se opera en los datos. En consecuencia, los protobufs usan una codificación que codifica enteros en 1, 2, 3, 4 o 5 bytes, y los enteros más pequeños toman menos bytes. Sin embargo, una vez que se recibe el mensaje, se descomprime en un formato entero de tamaño fijo más tradicional que es más fácil de operar. Es sólo durante la red transmisión que utilizan un entero de longitud variable de espacio eficiente.

 11
Author: Cort Ammon,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-06-13 03:57:59

Me gusta La analogía de la casa de Sergey, pero creo que una analogía del coche sería mejor.

Imagine tipos variables como tipos de automóviles y personas como datos. Cuando estamos buscando un coche nuevo, elegimos el que mejor se adapte a nuestro propósito. ¿Queremos un pequeño coche inteligente que solo puede caber una o dos personas? O una limusina para llevar a más gente? Ambos tienen sus ventajas e inconvenientes como la velocidad y el kilometraje de gas (piense en la velocidad y el uso de memoria).

Si tienes una limusina y estás conduciendo solo, no se va a encoger para que te quede solo a ti. Para hacer eso, usted tendría que vender el coche (léase: deallocate) y comprar uno nuevo más pequeño para usted.

Continuando con la analogía, puedes pensar en la memoria como un enorme estacionamiento lleno de autos, y cuando vas a leer, un chófer especializado entrenado únicamente para tu tipo de automóvil va a buscarlo para ti. Si su coche podría cambiar de tipo dependiendo de la gente dentro de él, tendría que traer una gran cantidad de choferes cada vez que quería consiga su coche puesto que nunca sabrían qué clase de coche estará sentado en el lugar.

En otras palabras, tratar de determinar cuánta memoria necesita leer en tiempo de ejecución sería enormemente ineficiente y superaría el hecho de que tal vez podría caber algunos autos más en su estacionamiento.

 11
Author: scohe001,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-06-14 06:06:07

Hay algunas razones. Una es la complejidad añadida para el manejo de números de tamaño arbitrario y el rendimiento que esto da porque el compilador ya no puede optimizar basándose en la suposición de que cada int es exactamente X bytes de largo.

Un segundo es que almacenar tipos simples de esta manera significa que necesitan un byte adicional para mantener la longitud. Por lo tanto, un valor de 255 o menos realmente necesita dos bytes en este nuevo sistema, no uno, y en el peor de los casos ahora necesita 5 bytes en lugar de 4. Esto significa que la ganancia de rendimiento en términos de memoria utilizada es menor de lo que podría pensar y en algunos casos extremos podría ser en realidad una pérdida neta.

Una tercera razón es que la memoria de la computadora es generalmente direccionable en palabras, no en bytes. (Pero véase la nota a pie de página). Las palabras son un múltiplo de bytes, generalmente 4 en sistemas de 32 bits y 8 en sistemas de 64 bits. Normalmente no puedes leer un byte individual, lees una palabra y extraes el enésimo byte de esa palabra. Esto significa tanto que la extracción individual los bytes de una palabra requieren un poco más de esfuerzo que solo leer la palabra completa y que es muy eficiente si toda la memoria se divide uniformemente en trozos de tamaño de palabra (es decir, de 4 bytes). Porque, si usted tiene números enteros de tamaño arbitrario flotando alrededor, usted podría terminar con una parte del entero que está en una palabra, y otra en la siguiente palabra, que necesita dos lecturas para obtener el entero completo.

Nota al pie: Para ser más precisos, mientras que se dirigió en bytes, la mayoría de los sistemas ignoraron el 'desigual' byte. Es decir, dirección 0, 1, 2 y 3 todos leen la misma palabra, 4, 5, 6 y 7 leen la siguiente palabra, y así sucesivamente.

En una nota no leated, esta es también la razón por la que los sistemas de 32 bits tenían un máximo de 4 GB de memoria. Los registros utilizados para direccionar ubicaciones en la memoria suelen ser lo suficientemente grandes como para contener una palabra, es decir, 4 bytes, que tiene un valor máximo de (2^32)-1 = 4294967295. 4294967296 bytes es 4 GB.

 10
Author: Buurman,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-06-13 07:59:35

Hay objetos que en algún sentido tienen tamaño variable, en la biblioteca estándar de C++, como std::vector. Sin embargo, todos estos asignan dinámicamente la memoria adicional que necesitarán. Si toma sizeof(std::vector<int>), obtendrá una constante que no tiene nada que ver con la memoria administrada por el objeto, y si asigna una matriz o estructura que contenga std::vector<int>, reservará este tamaño base en lugar de colocar el almacenamiento adicional en la misma matriz o estructura. Hay algunas piezas de sintaxis de C que admiten algo como esto, notablemente arrays y estructuras de longitud variable, pero C++ no optó por soportarlos.

El lenguaje estándar define el tamaño del objeto de esa manera para que los compiladores puedan generar código eficiente. Por ejemplo, si int tiene 4 bytes de largo en alguna implementación, y declaras a como un puntero o matriz de valores int, entonces a[i] se traduce en el pseudocódigo, " desreferenciar la dirección a + 4×i."Esto se puede hacer en tiempo constante, y es un operación común e importante que muchas arquitecturas de conjunto de instrucciones, incluidas x86 y las máquinas DEC PDP en las que se desarrolló originalmente C, pueden hacerlo en una sola instrucción de máquina.

Un ejemplo común del mundo real de datos almacenados consecutivamente como unidades de longitud variable son las cadenas codificadas como UTF-8. (Sin embargo, el tipo subyacente de una cadena UTF-8 para el compilador sigue siendo char y tiene ancho 1. Esto permite que las cadenas ASCII se interpreten como UTF-8 válido, y una gran cantidad de biblioteca código como strlen() y strncpy() para continuar trabajando.) La codificación de cualquier punto de código UTF-8 puede ser de uno a cuatro bytes de largo, y por lo tanto, si desea el quinto punto de código UTF-8 en una cadena, podría comenzar en cualquier lugar desde el quinto byte hasta el vigésimo byte de los datos. La única manera de encontrarlo es escanear desde el principio de la cadena y comprobar el tamaño de cada punto de código. Si desea encontrar el quinto grafema, también necesita comprobar las clases de caracteres. Si quisieras encontrar el millonésimo carácter UTF-8 en una cadena, que tendría que ejecutar este bucle un millón de veces! Si sabe que necesitará trabajar con índices a menudo, puede recorrer la cadena una vez y construir un índice de ella, o puede convertirla a una codificación de ancho fijo, como UCS-4. Encontrar el carácter millonésimo UCS - 4 en una cadena es solo cuestión de agregar cuatro millones a la dirección de la matriz.

Otra complicación con los datos de longitud variable es que, cuando los asigna, debe asignar tanta memoria como pueda usar, o bien reasignar dinámicamente según sea necesario. La asignación para el peor de los casos podría ser extremadamente derrochadora. Si necesita un bloque de memoria consecutivo, la reasignación podría obligarle a copiar todos los datos a una ubicación diferente, pero permitir que la memoria se almacene en trozos no consecutivos complica la lógica del programa.

Por lo tanto, es posible tener bignums de longitud variable en lugar de ancho fijo short int, int, long int y long long int, pero sería ser ineficiente para asignarlos y usarlos. Además, todas las CPU principales están diseñadas para hacer aritmética en registros de ancho fijo, y ninguna tiene instrucciones que operen directamente en algún tipo de bignum de longitud variable. Estos tendrían que ser implementados en software, mucho más lentamente.

En el mundo real, la mayoría (pero no todos) los programadores han decidido que los beneficios de la codificación UTF-8, especialmente la compatibilidad, son importantes, y que rara vez nos importa otra cosa que no sea escanear una cadena de adelante hacia atrás o copiar bloques de memoria que los inconvenientes de ancho variable son aceptables. Podríamos usar elementos empaquetados de ancho variable similares a UTF-8 para otras cosas. Pero muy rara vez lo hacemos, y no están en la biblioteca estándar.

 8
Author: Davislor,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-06-12 21:44:09

¿Por qué un tipo tiene solo un tamaño asociado cuando el espacio requerido para representar el valor podría ser menor que ese tamaño?

Principalmente debido a los requisitos de alineación.

Según básico.alinear/1:

Los tipos de objeto tienen requisitos de alineación que imponen restricciones a las direcciones en las que puede asignarse un objeto de ese tipo.

Piense en un edificio que tiene muchos pisos y cada piso tiene muchos habitación.
Cada habitación es su tamaño (un espacio fijo) capaz de contener N cantidad de personas u objetos.
Con el tamaño de la habitación conocido de antemano, hace que el componente estructural del edificio esté bien estructurado.

Si las habitaciones no están alineadas, entonces el esqueleto del edificio no estará bien estructurado.

 7
Author: codekaizer,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-06-12 14:56:25

Puede ser menos. Considere la función:

int foo()
{
    int bar = 1;
    int baz = 42;
    return bar+baz;
}

Compila a código ensamblador (g++, x64, detalles despojados)

$43, %eax
ret

Aquí, bar y baz terminan usando cero bytes para representar.

 7
Author: max630,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-06-14 10:55:47

Entonces, ¿por qué myInt no solo ocuparía 1 byte de memoria?

Porque le dijiste que usara tanto. Cuando se usa un unsigned int, algunos estándares dictan que se utilizarán 4 bytes y que el rango disponible para ello será de 0 a 4,294,967,295. Si fuera a usar un unsigned char en su lugar, probablemente solo usaría el byte 1 que está buscando (dependiendo del estándar y C++ normalmente usa estos estándares).

Si no fuera por estos estándares tendrías que tenga esto en cuenta: ¿cómo se supone que el compilador o la CPU saben que solo deben usar 1 byte en lugar de 4? Más adelante en su programa podría agregar o multiplicar ese valor, lo que requeriría más espacio. Cada vez que se hace una asignación de memoria, el sistema operativo tiene que encontrar, mapear, y darle ese espacio, (potencialmente el intercambio de memoria a la memoria RAM virtual, así); esto puede tomar mucho tiempo. Si asigna la memoria de antemano, no tendrá que esperar a que se complete otra asignación.

En cuanto a la razón por qué usamos 8 bits por byte, puede echar un vistazo a esto: ¿Cuál es la historia de por qué los bytes son ocho bits?

En una nota al margen, podría permitir que el entero se desbordara; pero si usa un entero con signo, los estándares de C\C++ establecen que los desbordamientos de enteros dan como resultado un comportamiento indefinido. Desbordamiento de enteros

 5
Author: Blerg,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-06-14 05:19:33

Algo simple que la mayoría de las respuestas parecen fallar:

porque se adapta a los objetivos de diseño de C++.

Ser capaz de calcular el tamaño de un tipo en tiempo de compilación permite que el compilador y el programador hagan un gran número de suposiciones simplificadoras, que traen muchos beneficios, particularmente con respecto al rendimiento. Por supuesto, los tipos de tamaño fijo tienen trampas concomitantes como desbordamiento de enteros. Esta es la razón por la que diferentes lenguajes toman diferentes decisiones de diseño. (Para instancia, los enteros de Python son esencialmente de tamaño variable.)

Probablemente la razón principal por la que C++ se inclina tan fuertemente hacia los tipos de tamaño fijo es su objetivo de compatibilidad con C. Sin embargo, dado que C++ es un lenguaje de tipo estático que intenta generar código muy eficiente, y evita agregar cosas no especificadas explícitamente por el programador, los tipos de tamaño fijo todavía tienen mucho sentido.

Entonces, ¿por qué C optó por tipos de tamaño fijo en primer lugar? Simple. Fue diseñado para escribir el funcionamiento de la era de los 70 sistemas, software de servidor y utilidades; cosas que proporcionaban infraestructura (como administración de memoria) para otro software. En un nivel tan bajo, el rendimiento es crítico, y también lo es el compilador haciendo precisamente lo que le dices.

 5
Author: Artelius,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-06-14 05:48:24

Cambiar el tamaño de una variable requeriría reasignación y esto generalmente no vale la pena los ciclos de CPU adicionales en comparación con el desperdicio de unos pocos bytes más de memoria.

Las variables locales van en una pila que es muy rápida de manipular cuando esas variables no cambian de tamaño. Si decidió que desea expandir el tamaño de una variable de 1 byte a 2 bytes, entonces tiene que mover todo en la pila por un byte para hacer ese espacio para ella. Que potencialmente puede costar una gran cantidad de CPU ciclos dependiendo de cuántas cosas necesitan ser movidas.

Otra forma de hacerlo es haciendo que cada variable sea un puntero a una ubicación de montón, pero desperdiciaría aún más ciclos de CPU y memoria de esta manera, en realidad. Los punteros son 4 bytes (direccionamiento de 32 bits) u 8 bytes (direccionamiento de 64 bits), por lo que ya está utilizando 4 u 8 para el puntero, luego el tamaño real de los datos en el montón. Todavía hay un costo de reasignación en este caso. Si necesita reasignar datos de montón, podría obtener suerte y tener espacio para expandirlo en línea, pero a veces tienes que moverlo a otro lugar en el montón para tener el bloque contiguo de memoria del tamaño que quieras.

Siempre es más rápido decidir cuánta memoria usar de antemano. Si puede evitar el tamaño dinámico, ganará rendimiento. Perder memoria generalmente vale la pena la ganancia de rendimiento. Es por eso que las computadoras tienen toneladas de memoria. :)

 5
Author: Chris Rollins,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-06-15 04:00:46

Al compilador se le permite hacer muchos cambios a su código, siempre y cuando las cosas sigan funcionando (la regla "tal cual").

Sería posible usar una instrucción de movimiento literal de 8 bits en lugar de la más larga (32/64 bits) requerida para mover un int completo. Sin embargo, necesitaría dos instrucciones para completar la carga, ya que tendría que establecer el registro a cero primero antes de hacer la carga.

Es simplemente más eficiente (al menos de acuerdo con los compiladores principales) manejar el valor como 32 bits. En realidad, aún no he visto un compilador x86 / x86_64 que hiciera una carga de 8 bits sin ensamblaje en línea.

Sin embargo, las cosas son diferentes cuando se trata de 64 bits. Al diseñar las extensiones anteriores (de 16 a 32 bits) de sus procesadores, Intel cometió un error. Aquí es una buena representación de lo que parece. La principal conclusión aquí es que cuando escribes a AL o AH, el otro no se ve afectado (es justo, ese era el punto y tenía sentido en ese entonces). Pero it se pone interesante cuando lo expandieron a 32 bits. Si escribes los bits inferiores (AL, AH o AX), no le pasa nada a los 16 bits superiores de EAX, lo que significa que si quieres promover un char en un int, necesitas limpiar esa memoria primero, pero no tienes forma de usar solo estos 16 bits superiores, haciendo que esta "característica" sea más un dolor que cualquier otra cosa.

Ahora con 64 bits, AMD hizo un trabajo mucho mejor. Si toca algo en los 32 bits inferiores, los 32 bits superiores simplemente se establecen en 0. Este conduce a algunas optimizaciones reales que se pueden ver en este godbolt . Puedes ver que cargar algo de 8 bits o 32 bits se hace de la misma manera, pero cuando usas variables de 64 bits, el compilador usa una instrucción diferente dependiendo del tamaño real de tu literal.

Como puede ver aquí, los compiladores pueden cambiar totalmente el tamaño real de su variable dentro de la CPU si produce el mismo resultado, pero no tiene sentido hacerlo para tipos más pequeños.

 3
Author: meneldal,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-06-15 05:40:00