Pregunta

He trabajado en proyectos para sistemas integrados en el pasado, donde hemos reorganizado el orden de declaración de las variables de pila para reducir el tamaño del ejecutable resultante. Por ejemplo, si tuviéramos:

void func()
{
    char c;
    int i;
    short s;
    ...
}

Nos gustaría reordenar esto para que sea:

void func()
{
    int i;
    short s;
    char c;
    ...
}

Debido a problemas de alineación, el primero resultó en el uso de 12 bytes de espacio de pila y el segundo solo en 8 bytes.

¿Es este comportamiento estándar para los compiladores de C o solo un defecto del compilador que estábamos usando?

Me parece que un compilador debería poder reordenar las variables de la pila para favorecer un tamaño ejecutable más pequeño si quisiera. Se me ha sugerido que algún aspecto del estándar C previene esto, pero no he podido encontrar una fuente confiable de ninguna manera.

Como pregunta adicional, ¿esto también se aplica a los compiladores de C ++?

Editar

Si la respuesta es sí, los compiladores de C / C ++ pueden reorganizar las variables de pila, ¿puede dar un ejemplo de un compilador que definitivamente hace esto? Me gustaría ver la documentación del compilador o algo similar que respalde esto.

Editar de nuevo

Gracias a todos por su ayuda. Para obtener documentación, lo mejor que he podido encontrar es el documento Asignación de ranura de pila óptima en GCC (pdf), de Naveen Sharma y Sanjiv Kumar Gupta, que se presentó en el procedimiento de la cumbre de GCC en 2003.

El proyecto en cuestión aquí estaba usando el compilador ADS para el desarrollo de ARM. En la documentación de ese compilador se menciona que las declaraciones de pedidos como las que he mostrado pueden mejorar el rendimiento, así como el tamaño de la pila, debido a cómo la arquitectura ARM-Thumb calcula las direcciones en el marco de pila local. Ese compilador no reorganizó automáticamente los locales para aprovechar esto. El documento vinculado aquí dice que a partir de 2003 GCC tampoco reorganizó el marco de la pila para mejorar la localidad de referencia para los procesadores ARM-Thumb, pero implica que podría hacerlo.

No puedo encontrar nada que diga que esto se implementó en GCC, pero creo que este documento cuenta como prueba de que estás en lo correcto. Gracias de nuevo.

¿Fue útil?

Solución

Como no hay nada en el estándar que prohíba que los compiladores C o C ++, sí, el compilador puede hacer eso.

Es diferente para los agregados (es decir, las estructuras), donde se debe mantener el orden relativo, pero aún así el compilador puede insertar bytes de relleno para lograr una alineación preferible.

Los compiladores de MSVC más nuevos de IIRC usan esa libertad en su lucha contra los desbordamientos de búferes de los locales.

Como nota al margen, en C ++, el orden de destrucción debe ser el orden inverso de la declaración, incluso si el compilador reordena el diseño de la memoria.

(No puedo citar el capítulo y el verso, sin embargo, esto es de memoria)

Otros consejos

El compilador no solo puede reordenar el diseño de pila de las variables locales, sino que también puede asignarlas a registros, asignarlos a vivir, a veces en registros y, a veces, en la pila, también puede asignar dos locales a la misma ranura en la memoria (si sus rangos de vida no se superponen) e incluso puede eliminar completamente las variables.

La pila ni siquiera necesita existir (de hecho, el estándar C99 no tiene una sola aparición de la palabra "pila"). Así que sí, el compilador es libre de hacer lo que quiera, siempre y cuando se mantenga la semántica de las variables con la duración del almacenamiento automático.

Como en un ejemplo: encontré muchas veces una situación en la que no podía mostrar una variable local en el depurador porque estaba almacenada en un registro.

El compilador es incluso libre de eliminar la variable de la pila y hacer que se registre solo si el análisis muestra que la dirección de la variable nunca se toma / usa.

Es posible que un compilador ni siquiera esté usando una pila para los datos. Si estás en una plataforma tan pequeña que te preocupas por 8 vs 12 bytes de pila, entonces es probable que haya compiladores que tengan enfoques bastante especializados. (Algunos compiladores PIC y 8051 vienen a la mente)

¿Para qué procesador estás compilando?

El compilador para la serie 62xx de DSP de Texas Instruments es capaz de, y lo hace "Optimización de todo el programa". (puedes apagarlo)

Aquí es donde su código se reorganiza, no solo los locales. Por lo tanto, el orden de ejecución no es exactamente lo que podría esperarse.

C y C ++ no en realidad prometen un modelo de memoria (en el sentido de JVM), por lo que las cosas pueden ser muy diferentes y aún legales.

Para aquellos que no los conocen, la familia 62xx tiene 8 instrucciones por ciclo de reloj DSP; a 750Mhz, hacen pico a 6e + 9 instrucciones. Algo del tiempo de todos modos. Hacen ejecución paralela, pero el orden de las instrucciones se realiza en el compilador, no en la CPU, como un Intel x86.

Los tableros integrados de PIC y Rabbit no tienen pilas a menos que las pidas especialmente bien.

es específico del compilador, uno puede hacer su propio compilador que haría lo inverso si lo quisiera de esa manera.

Un compilador decente pondrá variables locales en los registros si puede. Las variables solo deben colocarse en la pila si hay una presión de registro excesiva (no hay suficiente espacio) o si se toma la dirección de la variable, lo que significa que debe estar en la memoria.

Por lo que sé, no hay nada que diga que las variables deben colocarse en una ubicación específica o alineación en la pila para C / C ++; el compilador los colocará donde sea mejor para el rendimiento y / o lo que sea conveniente para los escritores del compilador.

AFAIK no hay nada en la definición de C o C ++ que especifique cómo el compilador debe ordenar las variables locales en la pila. Yo diría que confiar en lo que puede hacer el compilador en este caso es una mala idea, porque la próxima versión de su compilador puede hacerlo de manera diferente. Si dedica tiempo y esfuerzo a ordenar sus variables locales para guardar unos pocos bytes de pila, es mejor que esos pocos bytes sean realmente críticos para el funcionamiento de su sistema.

No hay necesidad de especulación ociosa sobre lo que el estándar de C requiere o no requiere: los borradores recientes están disponibles gratuitamente en línea en grupo de trabajo ANSI / ISO .

Esto no responde a tu pregunta, pero aquí están mis 2 centavos de dólar por un problema relacionado ...

No tuve el problema de la optimización del espacio de pila, pero tuve el problema de desalineación de las variables dobles en la pila. Se puede llamar a una función desde cualquier otra función y el valor del puntero de pila puede tener cualquier valor no alineado. Así que se me ha ocurrido la idea a continuación. Este no es el código original, simplemente lo escribí ...

#pragma pack(push, 16)

typedef struct _S_speedy_struct{

 double fval[4];
 int64  lval[4];
 int32  ival[8];

}S_speedy_struct;

#pragma pack(pop)

int function(...)
{
  int i, t, rv;
  S_speedy_struct *ptr;
  char buff[112]; // sizeof(struct) + alignment

  // ugly , I know , but it works...
  t = (int)buff;
  t +=  15; // alignment - 1
  t &= -16; // alignment
  ptr = (S_speedy_struct *)t;

  // speedy code goes on...
}
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top