efecto de paquete #pragma

9 minutos de lectura

avatar de usuario
cenoc

Me preguntaba si alguien podría explicarme qué es lo que #pragma pack la declaración del preprocesador lo hace y, lo que es más importante, por qué uno querría usarlo.

Revisé el página MSDN, que ofreció una idea, pero esperaba escuchar más de personas con experiencia. Lo he visto en código antes, aunque parece que ya no puedo encontrar dónde.

  • Fuerza una alineación/empaquetado particular de una estructura, pero como todos #pragma directivas que están definidas por la implementación.

    – sueño relajado

    23 de julio de 2010 a las 13:20


avatar de usuario
nick meyer

#pragma pack indica al compilador que empaquete los miembros de la estructura con una alineación particular. La mayoría de los compiladores, cuando declara una estructura, insertarán relleno entre los miembros para garantizar que estén alineados con las direcciones adecuadas en la memoria (generalmente un múltiplo del tamaño del tipo). Esto evita la penalización de rendimiento (o el error total) en algunas arquitecturas asociadas con el acceso a variables que no están alineadas correctamente. Por ejemplo, dados enteros de 4 bytes y la siguiente estructura:

struct Test
{
   char AA;
   int BB;
   char CC;
};

El compilador podría optar por colocar la estructura en la memoria de esta manera:

|   1   |   2   |   3   |   4   |  

| AA(1) | pad.................. |
| BB(1) | BB(2) | BB(3) | BB(4) | 
| CC(1) | pad.................. |

y sizeof(Test) sería 4 × 3 = 12, aunque solo contiene 6 bytes de datos. El caso de uso más común para el #pragma (que yo sepa) es cuando se trabaja con dispositivos de hardware en los que debe asegurarse de que el compilador no inserte relleno en los datos y cada miembro siga al anterior. Con #pragma pack(1)la estructura anterior se presentaría así:

|   1   |

| AA(1) |
| BB(1) |
| BB(2) |
| BB(3) |
| BB(4) |
| CC(1) |

Y sizeof(Test) sería 1 × 6 = 6.

Con #pragma pack(2)la estructura anterior se presentaría así:

|   1   |   2   | 

| AA(1) | pad.. |
| BB(1) | BB(2) |
| BB(3) | BB(4) |
| CC(1) | pad.. |

Y sizeof(Test) sería 2 × 4 = 8.

El orden de las variables en struct también es importante. Con variables ordenadas como sigue:

struct Test
{
   char AA;
   char CC;
   int BB;
};

y con #pragma pack(2)la estructura quedaría así:

|   1   |   2   | 

| AA(1) | CC(1) |
| BB(1) | BB(2) |
| BB(3) | BB(4) |

y sizeOf(Test) sería 3 × 2 = 6.

  • Podría valer la pena agregar las desventajas de empacar. (los accesos a objetos no alineados son lentos en el mejor caso, pero causará errores en algunas plataformas).

    – jalf

    23 de julio de 2010 a las 14:55

  • Parece que la “penalización de rendimiento” de las alineaciones mencionadas en realidad podría ser un beneficio en algunos sistemas danluu.com/3c-conflicto .

    usuario152949

    3 de enero de 2014 a las 15:22

  • @Pacerier No realmente. Esa publicación habla de una alineación bastante extrema (alineación en límites de 4 KB). La CPU espera ciertas alineaciones mínimas para varios tipos de datos, pero requieren, en el peor de los casos, una alineación de 8 bytes (sin contar los tipos de vectores que pueden requerir una alineación de 16 o 32 bytes). No alinearse en esos límites generalmente le da un impacto notable en el rendimiento (porque es posible que una carga deba realizarse como dos operaciones en lugar de una), pero el tipo está bien alineado o no lo está. Una alineación más estricta que eso no le compra nada (y arruina la utilización del caché

    – jalf

    14 mayo 2015 a las 18:30

  • En otras palabras, un doble espera estar en un límite de 8 bytes. Ponerlo en un límite de 7 bytes dañará el rendimiento. Pero ponerlo en un límite de 16, 32, 64 o 4096 bytes no le da nada por encima de lo que ya le dio el límite de 8 bytes. Obtendrá el mismo rendimiento de la CPU, mientras que obtendrá una utilización de caché mucho peor por las razones descritas en esa publicación.

    – jalf

    14 mayo 2015 a las 18:30


  • Entonces, la lección no es “empaquetar es beneficioso” (empaquetar viola la alineación natural de los tipos, por lo que perjudica el rendimiento), sino simplemente “no sobrealinee más allá de lo que se requiere”.

    – jalf

    14 mayo 2015 a las 18:32

avatar de usuario
nmichaels

#pragma se usa para enviar mensajes no portátiles (solo en este compilador) al compilador. Cosas como deshabilitar ciertas advertencias y empaquetar estructuras son razones comunes. Deshabilitar advertencias específicas es particularmente útil si compila con el indicador de advertencias como errores activado.

#pragma pack específicamente se usa para indicar que la estructura que se empaqueta no debe tener sus miembros alineados. Es útil cuando tiene una interfaz mapeada en memoria a una pieza de hardware y necesita poder controlar exactamente dónde apuntan los diferentes miembros de la estructura. En particular, no es una buena optimización de la velocidad, ya que la mayoría de las máquinas son mucho más rápidas para manejar datos alineados.

Para deshacer luego envolver #pragma pack(push,1) y #pragma pack(pop)

  • Para deshacer después, haga esto: #pragma pack(push,1) y #pragma pack(pop)

    – Malhal

    12 de enero de 2014 a las 20:14


  • @malhal Esto debería ser parte de una respuesta. Vine aquí buscando exactamente esto

    – MANA624

    22 de junio de 2021 a las 18:49


  • @MANA624 gracias agregué mi comentario a la respuesta

    – Malhal

    23 de junio de 2021 a las 13:05

Le dice al compilador el límite para alinear los objetos en una estructura. Por ejemplo, si tengo algo como:

struct foo { 
    char a;
    int b;
};

Con una máquina típica de 32 bits, normalmente “querría” tener 3 bytes de relleno entre a y b así que eso b aterrizará en un límite de 4 bytes para maximizar su velocidad de acceso (y eso es lo que normalmente sucederá de manera predeterminada).

Sin embargo, si tiene que hacer coincidir una estructura definida externamente, querrá asegurarse de que el compilador establezca su estructura exactamente de acuerdo con esa definición externa. En este caso, puede darle al compilador una #pragma pack(1) para contarlo no para insertar cualquier relleno entre miembros — si la definición de la estructura incluye relleno entre miembros, lo inserta explícitamente (por ejemplo, típicamente con miembros llamados unusedN o ignoreNo algo por el estilo).

  • “normalmente “querría” tener 3 bytes de relleno entre a y b para que b aterrice en un límite de 4 bytes para maximizar su velocidad de acceso”: ¿cómo maximizaría la velocidad de acceso tener 3 bytes de relleno?

    – Aswin

    31 de marzo de 2014 a las 13:04

  • @Ashwin: Colocación b en un límite de 4 bytes significa que el procesador puede cargarlo emitiendo una sola carga de 4 bytes. Aunque depende un poco del procesador, si está en un límite extraño, es muy probable que cargarlo requiera que el procesador emita dos instrucciones de carga separadas, luego use una palanca de cambios para juntar esas piezas. La penalización típica es del orden de una carga 3 veces más lenta de ese artículo.

    – Jerry Ataúd

    31 de marzo de 2014 a las 14:07

  • … si observa el código ensamblador para leer int alineados y no alineados, la lectura alineada suele ser un solo mnemotécnico. La lectura no alineada puede ser 10 líneas de ensamblaje fácilmente, ya que une el int, seleccionándolo byte por byte y colocándolo en las ubicaciones correctas del registro.

    – SF.

    12 de enero de 2016 a las 14:09

  • @SF .: Puede ser, pero incluso cuando no lo es, no se deje engañar, en una CPU x86 (para un ejemplo obvio), las operaciones se llevan a cabo en el hardware, pero aún obtiene aproximadamente el mismo conjunto de operaciones y desaceleración.

    – Jerry Ataúd

    12 de enero de 2016 a las 16:12

He visto a personas usarlo para asegurarse de que una estructura tome una línea de caché completa para evitar el intercambio falso en un contexto de subprocesos múltiples. Si va a tener una gran cantidad de objetos que se empaquetarán libremente de forma predeterminada, podría ahorrar memoria y mejorar el rendimiento de la memoria caché para empaquetarlos más apretados, aunque el acceso a la memoria no alineado generalmente ralentizará las cosas, por lo que podría haber un inconveniente.

El compilador podría alinear miembros en estructuras para lograr el máximo rendimiento en una determinada plataforma. #pragma pack directiva le permite controlar esa alineación. Por lo general, debe dejarlo por defecto para un rendimiento óptimo. Si necesita pasar una estructura a la máquina remota, generalmente usará #pragma pack 1 para excluir cualquier alineación no deseada.

avatar de usuario
Ponto Gagge

Los elementos de datos (p. ej., miembros de clases y estructuras) normalmente se alinean en los límites de WORD o DWORD para los procesadores de la generación actual con el fin de mejorar los tiempos de acceso. Recuperar un DWORD en una dirección que no es divisible por 4 requiere al menos un ciclo de CPU adicional en un procesador de 32 bits. Entonces, si tiene, por ejemplo, tres miembros char char a, b, c;en realidad tienden a ocupar 6 o 12 bytes de almacenamiento.

#pragma le permite anular esto para lograr un uso de espacio más eficiente, a expensas de la velocidad de acceso, o para la consistencia de los datos almacenados entre diferentes objetivos del compilador. Me divertí mucho con esta transición de código de 16 bits a 32 bits; Espero que la migración a código de 64 bits cause el mismo tipo de dolores de cabeza para algunos códigos.

avatar de usuario
clifford

un compilador puede colocar miembros de la estructura en límites de bytes particulares por razones de rendimiento en una arquitectura particular. Esto puede dejar relleno sin usar entre los miembros. El empaquetamiento de la estructura obliga a los miembros a ser contiguos.

Esto puede ser importante, por ejemplo, si necesita que una estructura se ajuste a un archivo o formato de comunicaciones en particular donde los datos que necesita estén en posiciones específicas dentro de una secuencia. Sin embargo, dicho uso no se ocupa de los problemas de endianidad, por lo que, aunque se use, es posible que no sea portátil.

También puede superponerse exactamente a la estructura de registro interna de algún dispositivo de E/S, como un UART o un controlador USB, por ejemplo, para que el acceso al registro sea a través de una estructura en lugar de direcciones directas.

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad