c++ - parametros - ¿Puede un compilador de C reorganizar las variables de la pila?
librerias de c++ (11)
AFAIK no hay nada en la definición de C o C ++ que especifique cómo el compilador debe ordenar variables locales en la pila. Diría que confiar en lo que el compilador puede hacer 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 salvar unos pocos bytes de la pila, esos pocos bytes deberían ser realmente críticos para el funcionamiento de su sistema.
He trabajado en proyectos para sistemas integrados en el pasado donde hemos reorganizado el orden de declaración de variables de pila para disminuir el tamaño del ejecutable resultante. Por ejemplo, si tuviéramos:
void func()
{
char c;
int i;
short s;
...
}
Reordenaríamos esto para que sea:
void func()
{
int i;
short s;
char c;
...
}
Debido a problemas de alineación, el primero resultó en 12 bytes de espacio de pila y el segundo resultó en solo 8 bytes.
¿Es este comportamiento estándar para los compiladores de C o simplemente una deficiencia 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 de ejecutable más pequeño si así lo deseara. Se me ha sugerido que algún aspecto del estándar C impide esto, pero no he podido encontrar una fuente confiable de ninguna manera.
Como una pregunta de bonificación, ¿esto también se aplica a los compiladores de C ++?
Editar
Si la respuesta es sí, los compiladores C / C ++ pueden reordenar las variables de la pila, ¿puedes dar un ejemplo de un compilador que definitivamente hace esto? Me gustaría ver documentación del compilador o algo similar que respalde esto.
Editar nuevamente
Gracias a todos por su ayuda. Para la documentación, lo mejor que he podido encontrar es el trabajo Optimal Stack Slot Assignment en GCC (pdf), de Naveen Sharma y Sanjiv Kumar Gupta, que se presentó en los procedimientos de la cumbre del GCC en 2003.
El proyecto en cuestión aquí estaba usando el compilador de ADS para el desarrollo de ARM. En la documentación del compilador se menciona que el pedido de declaraciones como las que he mostrado puede mejorar el rendimiento, así como el tamaño de la pila, debido a la forma en que la arquitectura ARM-Thumb calcula las direcciones en el marco de la pila local. Ese compilador no reorganizó automáticamente a los lugareños para aprovechar esto. El documento vinculado aquí dice que a partir de 2003 GCC tampoco reorganizó el marco de pila para mejorar la localidad de referencia para los procesadores ARM-Thumb, pero implica que usted podría hacerlo.
No puedo encontrar nada que definitivamente diga que esto se implementó alguna vez en GCC, pero creo que este documento cuenta como prueba de que estás en lo correcto. Gracias de nuevo.
Como no hay nada en la norma que prohíba eso para los compiladores C o C ++, sí, el compilador puede hacerlo.
Es diferente para los agregados (es decir, las estructuras), donde se debe mantener el orden relativo, pero aun así el compilador puede insertar los bytes del pad para lograr una alineación preferible.
Los nuevos compiladores de MSVC de IIRC usan esa libertad en su lucha contra los desbordamientos de buffer 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 versículo, sin embargo, esto es de memoria).
El compilador de la serie de instrumentos DS62 de Texas Instruments es capaz de "optimizar 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 se espera.
C y C ++ en realidad no prometen un modelo de memoria (en el sentido de, por ejemplo, la JVM), por lo que las cosas pueden ser bastante diferentes y aún legales.
Para aquellos que no los conocen, la familia 62xx tiene 8 instrucciones por ciclo de reloj DSP; a 750Mhz, alcanzan un máximo de 6e + 9 instrucciones. Algunas veces, de todos modos. Ejecutan la ejecución en paralelo, pero el orden de las instrucciones se realiza en el compilador, no en la CPU, como un Intel x86.
Los tableros incrustados PIC y Rabbit no tienen pilas a menos que lo pidas especialmente.
El compilador incluso puede 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.
La pila ni siquiera necesita existir (de hecho, el estándar C99 no tiene una sola ocurrencia de la palabra "pila"). Entonces, sí, el compilador puede hacer lo que quiera siempre que eso preserve la semántica de las variables con duración de almacenamiento automática.
Como un ejemplo: me encontré muchas veces con una situación en la que no podía mostrar una variable local en el depurador porque estaba almacenada en un registro.
No hay necesidad de especulaciones ociosas sobre lo que el estándar C requiere o no requiere: los borradores recientes están disponibles gratuitamente en línea desde el grupo de trabajo ANSI / ISO .
Un compilador decente colocará variables locales en registros si puede. Las variables solo deben colocarse en la pila si hay una presión de registro excesiva (no hay espacio suficiente) o si se toma la dirección de la variable, lo que significa que debe vivir en la memoria.
Por lo que yo sé, no hay nada que diga que las variables deben ubicarse en cualquier 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.
Un compilador puede que ni siquiera esté usando una pila para datos. Si estás en una plataforma tan pequeña que te preocupan por 8 contra 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?
son las especificaciones del compilador, uno puede hacer su propio compilador que haría lo inverso si así lo quisiera.
Esto no responde a su pregunta, pero aquí están mis 2 centavos sobre un problema relacionado ...
No tuve el problema de la optimización del espacio de la pila, pero tuve el problema de la mala alineación de las variables dobles en la pila. Se puede invocar una función desde cualquier otra función y el valor del puntero de pila puede tener cualquier valor desalineado. Así que se me ocurrió la idea a continuación. Este no es el código original, solo 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...
}
El compilador no solo puede reordenar el diseño de pila de las variables locales, puede asignarlas a registros, asignarlas a Live en ocasiones en registros y, a veces en la pila, puede asignar dos locales a la misma ranura en la memoria (si sus rangos de vida no se superponen) y puede incluso eliminar completamente las variables.