programas programar lenguaje ejemplos descargar como comandos codigos basicos c struct compiler-optimization memory-alignment

programar - ejemplos de programas en c++ pdf



¿Por qué los compiladores de C no pueden reorganizar los miembros de la estructura para eliminar el relleno de alineación? (11)

Al no ser miembro del WG14, no puedo decir nada definitivo, pero tengo mis propias ideas:

  1. Violaría el principio de la menor sorpresa: puede haber una maldita buena razón por la cual quiero exponer mis elementos en un orden específico, independientemente de si es o no el más eficiente en el uso del espacio, y no me gustaría que el compilador reorganice. esos elementos;

  2. Tiene el potencial de romper una cantidad no trivial de código existente. Hay una gran cantidad de código heredado que depende de que la dirección de la estructura sea la misma que la dirección del primer miembro (vio una gran cantidad de MacOS clásicos). código que hizo esa suposición);

La Razón de C99 aborda directamente el segundo punto ("El código existente es importante, las implementaciones existentes no lo son") e indirectamente aborda el primero ("Confía en el programador").

Posible duplicado:
¿Por qué GCC no optimiza las estructuras?
¿Por qué C ++ no hace que la estructura sea más estricta?

Considere el siguiente ejemplo en una máquina x86 de 32 bits:

Debido a las restricciones de alineación, la siguiente estructura

struct s1 { char a; int b; char c; char d; char e; }

podría representarse con mayor eficiencia de memoria (12 vs. 8 bytes) si los miembros se reordenaron como en

struct s2 { int b; char a; char c; char d; char e; }

Sé que los compiladores C / C ++ no pueden hacer esto. Mi pregunta es por qué el lenguaje fue diseñado de esta manera. Después de todo, podemos terminar desperdiciando grandes cantidades de memoria, y referencias como struct_ref->b no se preocuparán por la diferencia.

EDITAR : Gracias a todos por sus respuestas extremadamente útiles. Explica muy bien por qué la reorganización no funciona debido a la forma en que se diseñó el lenguaje. Sin embargo, me hace pensar: ¿resistirían estos argumentos si la reorganización fuera parte del lenguaje? Digamos que había una regla de reordenamiento específica, de la cual necesitábamos al menos eso

  1. solo deberíamos reorganizar la estructura si es realmente necesario (no hacer nada si la estructura ya está "ajustada")
  2. la regla solo mira la definición de la estructura, no dentro de las estructuras internas. Esto garantiza que un tipo de estructura tenga el mismo diseño, ya sea que sea interno o no en otra estructura
  3. el diseño de la memoria compilada de una estructura dada es predecible dada su definición (es decir, la regla es fija)

Dirigiendo tus argumentos uno por uno, razono:

  • Mapeo de datos de bajo nivel, "elemento de menor sorpresa" : simplemente escriba sus estructuras en un estilo ajustado usted mismo (como en la respuesta de @ Perry) y nada ha cambiado (requisito 1). Si, por alguna extraña razón, desea que el relleno interno esté allí, puede insertarlo manualmente utilizando variables ficticias, y / o podría haber palabras clave / directivas.

  • Diferencias del compilador : el requisito 3 elimina esta preocupación. De hecho, según los comentarios de @David Heffernan, parece que tenemos este problema hoy porque los compiladores diferentes se distribuyen de forma diferente.

  • Optimización : todo el punto de reordenamiento es la optimización (de memoria). Veo mucho potencial aquí. Es posible que no podamos eliminar el relleno por completo, pero no veo cómo el reordenamiento podría limitar la optimización de ninguna manera.

  • Tipo de casting : me parece que este es el mayor problema. Aún así, debería haber formas de evitar esto. Como las reglas están fijadas en el idioma, el compilador puede descifrar cómo se reordenaron los miembros y reaccionar en consecuencia. Como se mencionó anteriormente, siempre será posible evitar el reordenamiento en los casos en que desee un control completo. Además, el requisito 2 asegura que el código de tipo seguro nunca se romperá.

La razón por la que creo que tal regla podría tener sentido es porque me parece más natural agrupar a los miembros de la estructura por sus contenidos que por sus tipos. También es más fácil para el compilador elegir el mejor orden que para mí cuando tengo muchas estructuras internas. El diseño óptimo puede incluso ser uno que no puedo expresar de forma segura. Por otro lado, parecería complicar el lenguaje, lo cual es un inconveniente.

Tenga en cuenta que no estoy hablando de cambiar el idioma, solo si podría (/ debería) haber sido diseñado de manera diferente.

Sé que mi pregunta es hipotética, pero creo que la discusión proporciona una visión más profunda en los niveles más bajos de la máquina y el diseño del lenguaje.

Soy bastante nuevo aquí, así que no sé si debería generar una nueva pregunta para esto. Por favor dime si este es el caso.


C [y C ++] son ​​considerados como lenguajes de programación de sistemas, por lo que proporcionan acceso de bajo nivel al hardware, por ejemplo, memoria mediante punteros. El programador puede acceder a un fragmento de datos y convertirlo en una estructura y acceder a varios miembros [fácilmente].

Otro ejemplo es una estructura como la siguiente, que almacena datos de tamaño variable.

struct { uint32_t data_size; uint8_t data[1]; // this has to be the last member } _vv_a;


C está diseñado y diseñado para permitir la escritura de hardware no portátil y el código dependiente de formato en un lenguaje de alto nivel. La reorganización de los contenidos de la estructura detrás de la parte posterior del programador destruiría esa capacidad.

Observe este código real de ip.h de NetBSD:

/* * Structure of an internet header, naked of options. */ struct ip { #if BYTE_ORDER == LITTLE_ENDIAN unsigned int ip_hl:4, /* header length */ ip_v:4; /* version */ #endif #if BYTE_ORDER == BIG_ENDIAN unsigned int ip_v:4, /* version */ ip_hl:4; /* header length */ #endif u_int8_t ip_tos; /* type of service */ u_int16_t ip_len; /* total length */ u_int16_t ip_id; /* identification */ u_int16_t ip_off; /* fragment offset field */ u_int8_t ip_ttl; /* time to live */ u_int8_t ip_p; /* protocol */ u_int16_t ip_sum; /* checksum */ struct in_addr ip_src, ip_dst; /* source and dest address */ } __packed;

Esa estructura es idéntica en diseño al encabezado de un datagrama IP. Se usa para interpretar directamente blobs de memoria grabados por un controlador ethernet como encabezados de datagramas IP. Imagínese si el compilador arregló arbitrariamente el contenido por debajo del autor, sería un desastre.

Y sí, no es precisamente portable (e incluso hay una directiva gcc no portátil dada allí a través de la macro __packed ) pero ese no es el punto. C está específicamente diseñado para permitir la escritura de código de alto nivel no portátil para el hardware de manejo. Esa es su función en la vida.


Cambiaría la semántica de las operaciones del puntero para reordenar los miembros de la estructura. Si le importa la representación de memoria compacta, es su responsabilidad como programador conocer su arquitectura de destino y organizar sus estructuras en consecuencia.


Esta es una razón que no he visto hasta ahora: sin reglas de reorganización estándar, rompería la compatibilidad entre los archivos de origen.

Supongamos que una estructura se define en un archivo de cabecera y se utiliza en dos archivos.
Ambos archivos se compilan por separado y luego se vinculan. La compilación puede ser en diferentes momentos (tal vez tocó solo uno, por lo que tuvo que ser recompilado), posiblemente en diferentes computadoras (si los archivos están en una unidad de red) o incluso versiones de compilador diferentes.
Si en algún momento, el compilador decide reordenar, y en otro no, los dos archivos no acordarán dónde están los campos.

Como ejemplo, piense en la llamada al sistema stat y struct stat .
Cuando instala Linux (por ejemplo), obtiene libC, que incluye stat , que alguna vez compiló alguien.
Luego compila una aplicación con su compilador, con sus indicadores de optimización, y espera que ambos acuerden el diseño de la estructura.


Hay varias razones por las cuales el compilador de C no puede reordenar automáticamente los campos:

  • El compilador de C no sabe si la struct representa la estructura de memoria de los objetos más allá de la unidad de compilación actual (por ejemplo: una biblioteca extranjera, un archivo en disco, datos de red, tablas de páginas de CPU, ...). En tal caso, la estructura binaria de los datos también se define en un lugar inaccesible para el compilador, por lo que reordenar los campos de struct crearía un tipo de datos que es inconsistente con las otras definiciones. Por ejemplo, el encabezado de un archivo en un archivo ZIP contiene múltiples campos desalineados de 32 bits. Reordenar los campos haría imposible que el código C leyera o escribiera directamente el encabezado (suponiendo que la implementación de ZIP quisiera acceder a los datos directamente):

    struct __attribute__((__packed__)) LocalFileHeader { uint32_t signature; uint16_t minVersion, flag, method, modTime, modDate; uint32_t crc32, compressedSize, uncompressedSize; uint16_t nameLength, extraLength; };

    El atributo packed evita que el compilador alinee los campos de acuerdo con su alineación natural, y no tiene relación con el problema del orden de campo. Sería posible reordenar los campos de LocalFileHeader para que la estructura tenga un tamaño mínimo y tenga todos los campos alineados con su alineación natural. Sin embargo, el compilador no puede elegir reordenar los campos porque no sabe que la estructura realmente está definida por la especificación del archivo ZIP.

  • C es un lenguaje inseguro. El compilador de C no sabe si se accederá a los datos a través de un tipo diferente al visto por el compilador, por ejemplo:

    struct S { char a; int b; char c; }; struct S_head { char a; }; struct S_ext { char a; int b; char c; int d; char e; }; struct S s; struct S_head *head = (struct S_head*)&s; fn1(head); struct S_ext ext; struct S *sp = (struct S*)&ext; fn2(sp);

    Este es un patrón de programación de bajo nivel ampliamente utilizado , especialmente si el encabezado contiene el tipo ID de los datos ubicados más allá del encabezado.

  • Si un tipo de struct está incrustado en otro tipo de struct , es imposible alinear la struct interna:

    struct S { char a; int b; char c, d, e; }; struct T { char a; struct S s; // Cannot inline S into T, ''s'' has to be compact in memory char b; };

    Esto también significa que mover algunos campos de S a una estructura separada desactiva algunas optimizaciones:

    // Cannot fully optimize S struct BC { int b; char c; }; struct S { char a; struct BC bc; char d, e; };

  • Debido a que la mayoría de los compiladores de C están optimizando los compiladores, el reordenamiento de los campos de estructura requeriría la implementación de nuevas optimizaciones. Es cuestionable si esas optimizaciones podrían funcionar mejor de lo que los programadores pueden escribir. Diseñar estructuras de datos a mano consume mucho menos tiempo que otras tareas del compilador, como asignación de registros, alineación de funciones, plegado constante, transformación de una instrucción de conmutación en búsqueda binaria, etc. De este modo se obtienen beneficios al permitir al compilador optimizar las estructuras de datos parecen ser menos tangibles que las optimizaciones del compilador tradicionales.


Las estructuras se utilizan para representar el hardware físico en los niveles más bajos. Como tal, el compilador no puede mover las cosas una ronda para adaptarse a ese nivel.

Sin embargo, no sería irrazonable tener un #pragma que permita al compilador reorganizar estructuras basadas en la memoria que solo se utilizan internamente en el programa. Sin embargo, no sé de una bestia así (pero eso no significa sentadillas, estoy fuera de contacto con C / C ++)


Si estuviera leyendo / escribiendo datos binarios a / desde estructuras C, el reordenamiento de los miembros de la struct sería un desastre. No habría una forma práctica de poblar realmente la estructura desde un búfer, por ejemplo.


Su caso es muy específico, ya que requeriría que se reordene el primer elemento de una struct . Esto no es posible, ya que el elemento que se define primero en una struct debe estar siempre en el desplazamiento 0 . Una gran cantidad de código (falso) se rompería si esto estuviera permitido.

En general, los punteros de subobjetos que viven dentro del mismo objeto más grande siempre deben permitir la comparación de punteros. Me imagino que algún código que use esta característica se rompería si invirtiera la orden. Y para esa comparación, el conocimiento del compilador en el punto de definición no ayudaría: un puntero a un subobjeto no tiene una "marca" a qué objeto más grande pertenece. Cuando se pasa a otra función como tal, se pierde toda la información de un contexto posible.


Tenga en cuenta que una declaración de variable, como una estructura, está diseñada para ser una representación "pública" de la variable. No solo es utilizado por su compilador, sino que también está disponible para otros compiladores que representa ese tipo de datos. Probablemente terminará en un archivo .h. Por lo tanto, si un compilador se va a tomar libertades con la forma en que están organizados los miembros dentro de una estructura, TODOS los compiladores deben poder seguir las mismas reglas. De lo contrario, como se ha mencionado, la aritmética del puntero se confundirá entre los diferentes compiladores.


supongamos que tiene un encabezado ah con

struct s1 { char a; int b; char c; char d; char e; }

y esto es parte de una biblioteca separada (de la cual solo tiene los binarios compilados compilados por un compilador desconocido) y desea usar esta estructura para comunicarse con esta biblioteca,

si el compilador puede reordenar los miembros de la forma que le plazca, esto será imposible ya que el compilador del cliente no sabe si usar la estructura tal como está o si está optimizada (y luego b ir delante o atrás) o incluso completamente acolchado con cada miembro alineado en intervalos de 4 bytes

para resolver esto puede definir un algoritmo determinista para compactar, pero eso requiere que todos los compiladores lo implementen y que el algoritmo sea bueno (en términos de eficiencia). es más fácil simplemente acordar las reglas de relleno que en reordenar

es fácil agregar un #pragma que prohíbe la optimización para cuando necesita que el diseño de una estructura específica sea exactamente lo que necesita, así que no hay problema