Una cosa que siempre me pareció intuitivamente una característica positiva de C (bueno, en realidad de sus implementaciones como gcc, clang, ...) es el hecho de que no almacena ninguna información oculta junto a sus propias variables en tiempo de ejecución. Con esto quiero decir que si, por ejemplo, desea una variable "x" del tipo "uint16_t", puede estar seguro de que "x" solo ocupará 2 bytes de espacio (y no llevará ninguna información oculta como su tipo, etc. .). Del mismo modo, si desea una matriz de 100 enteros, puede estar seguro de que es tan grande como 100 enteros.
Sin embargo, cuanto más intento encontrar casos de uso concretos para esta característica, más me pregunto si realmente tiene alguna ventaja práctica. Lo único que se me ocurrió hasta ahora es que obviamente necesita menos RAM. Para entornos limitados, como chips AVR, etc., esta es definitivamente una gran ventaja, pero para los casos de uso cotidiano de escritorio / servidor, parece ser bastante irrelevante. Otra posibilidad en la que estoy pensando es que podría ser útil / crucial para acceder al hardware, o tal vez mapear regiones de memoria (por ejemplo, para salida VGA y similares) ...?
Mi pregunta: ¿Hay dominios concretos que no pueden o pueden implementarse muy engorrosamente sin esta función?
PD ¡Por favor dime si tienes un nombre mejor para él! ;)
fuente
virtual
función miembro. Por lo tanto, RTTI nunca aumenta el tamaño de ningún objeto, solo hace que el binario sea más grande por una constante.T *
siempre es del mismo tamaño yT
puede contener un campo oculto que apunta a la tabla. Y ningún compilador de C ++ insertó vtables en objetos que no los necesitan.Respuestas:
Hay varios beneficios, el obvio es en tiempo de compilación para garantizar que cosas como los parámetros de función coincidan con los valores que se pasan.
Pero creo que estás preguntando qué está sucediendo en tiempo de ejecución.
Tenga en cuenta que el compilador creará un tiempo de ejecución que integra el conocimiento de los tipos de datos en las operaciones que realiza. Es posible que cada fragmento de datos en la memoria no se describa por sí mismo, pero el código inherentemente sabe cuáles son esos datos (si ha realizado su trabajo correctamente).
En tiempo de ejecución las cosas son un poco diferentes de lo que piensas.
Por ejemplo, no asuma que solo se usan dos bytes cuando declara uint16_t. Dependiendo del procesador y la alineación de palabras, puede ocupar 16, 32 o 64 bits en la pila. Puede descubrir que su conjunto de cortos consume mucha más memoria de la que esperaba.
Esto puede ser problemático en ciertas situaciones en las que necesita hacer referencia a datos en desplazamientos específicos. Esto sucede cuando se comunica entre dos sistemas que tienen arquitecturas de procesador diferentes, ya sea a través de un enlace inalámbrico o mediante archivos.
C le permite especificar estructuras con granularidad de nivel de bits:
Esta estructura tiene una longitud de tres bytes, con un corto definido para comenzar en un desplazamiento impar. También deberá empacarse para que sea exactamente como lo definió. De lo contrario, el compilador alineará las palabras con los miembros.
El compilador generará código detrás de escena para extraer estos datos y copiarlos en un registro para que pueda hacer cosas útiles con ellos.
Ahora puede ver que cada vez que mi programa accede a un miembro de la estructura myMessage, sabrá exactamente cómo extraerlo y operarlo.
Esto puede volverse problemático y difícil de administrar cuando se comunica entre diferentes sistemas con diferentes versiones de software. Debe diseñar cuidadosamente el sistema y el código para garantizar que ambas partes tengan exactamente la misma definición de los tipos de datos. Esto puede ser bastante desafiante en algunos entornos. Aquí es donde necesita un mejor protocolo que contenga datos autodescriptivos, como los Buffers de protocolo de Google .
Por último, hace un buen punto para preguntar qué tan importante es esto en el entorno de escritorio / servidor. Realmente depende de la cantidad de memoria que planea usar. Si está haciendo algo como el procesamiento de imágenes, puede terminar usando una gran cantidad de memoria que puede afectar el rendimiento de su aplicación. Definitivamente, esto siempre es una preocupación en el entorno integrado donde la memoria está restringida y no hay memoria virtual.
fuente
short
. Pero este es un requisito único para el inicio de la matriz, el resto se alinea automáticamente correctamente en virtud de ser consecutivo.uint8_t padding: 6;
, al igual que los primeros dos bits. O, más claramente, solo el comentario//6 bits of padding inserted by the compiler
. La estructura, como la ha escrito, tiene un tamaño de al menos nueve bytes, no tres.Llegas a una de las únicas razones por las que esto es útil: mapear estructuras de datos externas. Estos incluyen memorias intermedias de video mapeadas en memoria, registros de hardware, etc. También incluyen datos transmitidos intactos fuera del programa, como certificados SSL, paquetes IP, imágenes JPEG y casi cualquier otra estructura de datos que tenga una vida persistente fuera del programa.
fuente
C es un lenguaje de bajo nivel, casi un ensamblador portátil, por lo que sus estructuras de datos y construcciones de lenguaje están cerca del metal (las estructuras de datos no tienen costos ocultos, excepto el relleno, la alineación y las restricciones de tamaño impuestas por el hardware y ABI ). Por lo tanto, C no tiene una escritura dinámica nativa. Pero si lo necesita, podría adoptar una convención de que todos sus valores son agregados comenzando con alguna información de tipo (por ejemplo, algunos
enum
...); useunion
-s y (para cosas similares a una matriz) miembro de matriz flexible alstruct
contener también el tamaño de la matriz.(cuando programe en C, es su responsabilidad definir, documentar y seguir convenciones útiles, especialmente condiciones previas y posteriores e invariantes; también la asignación dinámica de memoria en C requiere convenciones explícitas sobre quién debería tener
free
algunamalloc
zona de memoria activada )Por lo tanto, para representar los valores que son números enteros en caja, o cadenas, o algún tipo de esquema -como símbolo , o vectores de valores, que va a utilizar conceptualmente una unión etiquetada (implementado como una unión de punteros) -siempre empezando por el tipo de tipo -, p.ej:
Para obtener el tipo dinámico de algún valor
Aquí hay un "reparto dinámico" de vectores:
y un "acceso seguro" dentro de los vectores:
Por lo general, definirá la mayoría de las funciones cortas anteriores como
static inline
en algún archivo de encabezado.Por cierto, si puede usar el recolector de basura de Boehm, puede codificar con bastante facilidad en un estilo de nivel superior (pero inseguro), y varios intérpretes de Scheme se hacen de esa manera. Un constructor de vectores variados podría ser
y si tienes tres variables
podrías construir un vector a partir de ellos usando
make_vector(3,v1,v2,v3)
Si no desea utilizar el recolector de basura de Boehm (o diseñar el suyo propio), debe tener mucho cuidado al definir los destructores y documentar quién, cómo y cuándo la memoria debe ser
free
-d; Mira este ejemplo. Por lo tanto, puede usarmalloc
(pero luego probar contra su falla) en lugar de loGC_MALLOC
anterior, pero necesita definir cuidadosamente y usar alguna función destructoravoid destroy_value(value_t)
La fortaleza de C es ser lo suficientemente bajo como para hacer posible un código como el anterior y definir sus propias convenciones (en particular para su software).
fuente