Intentando entender la opción de gcc-fomit-frame-pointer
gcc flags (2)
La mayoría de las funciones más pequeñas no necesitan un puntero de marco: las funciones más grandes PUEDEN necesitarlo.
Se trata de qué tan bien el compilador logra rastrear cómo se usa la pila y dónde están las cosas en la pila (variables locales, argumentos pasados a la función actual y argumentos preparados para una función a punto de ser llamada). No creo que sea fácil caracterizar las funciones que necesitan o no necesitan un puntero de marco (técnicamente, la función NO TIENE que tener un puntero de marco; es más un caso de "si el compilador lo considera necesario para reducir la complejidad de otro código ").
No creo que deba "intentar hacer que las funciones no tengan un puntero de marco" como parte de su estrategia para la codificación, como dije, las funciones simples no las necesitan, así que use -fomit-frame-pointer
, y usted " Obtendré un registro más disponible para el asignador de registro, y guardaré 1-3 instrucciones sobre la entrada / salida de las funciones. Si su función necesita un puntero de marco, es porque el compilador decide que es una mejor opción que no usar un puntero de marco. No es un objetivo tener funciones sin un puntero de marco; es un objetivo tener un código que funcione de manera correcta y rápida.
Tenga en cuenta que "no tener un puntero de marco" debería ofrecer un mejor rendimiento, pero no es una bala mágica que proporcione enormes mejoras, particularmente no en x86-64, que ya cuenta con 16 registros para comenzar. En 32-bit x86, ya que solo tiene 8 registros, uno de los cuales es el puntero de pila, y tomando otro como puntero de marco significa que se toma el 25% del espacio de registro. Cambiar eso a 12.5% es una gran mejora. Por supuesto, compilar para 64 bits también ayudará bastante.
Le pedí a Google que me diera el significado de la opción gcc
-fomit-frame-pointer
, que me redirige a la siguiente declaración.
-fomit-frame-puntero
No mantenga el puntero del marco en un registro para las funciones que no lo necesitan. Esto evita las instrucciones para guardar, configurar y restaurar punteros de cuadro; también hace que un registro adicional esté disponible en muchas funciones. También hace que la depuración sea imposible en algunas máquinas.
Según mi conocimiento de cada función, se creará un registro de activación en la pila de la memoria de proceso para mantener todas las variables locales y algo más de información. Espero que este puntero de marco signifique la dirección del registro de activación de una función.
En este caso, ¿cuál es el tipo de funciones para las cuales no es necesario mantener el puntero del marco en un registro? Si obtengo esta información, intentaré diseñar la nueva función basándose en eso (si es posible) porque si el puntero del marco no se guarda en los registros, algunas instrucciones se omitirán en binario. Esto realmente mejorará el rendimiento notablemente en una aplicación donde hay muchas funciones.
Todo se trata del registro BP / EBP / RBP en plataformas Intel. Este registro se establece de forma predeterminada en el segmento de pila (no necesita un prefijo especial para acceder al segmento de pila).
El EBP es la mejor opción de registro para acceder a estructuras de datos, variables y espacio de trabajo dinámicamente asignado dentro de la pila. EBP a menudo se utiliza para acceder a los elementos en la pila en relación con un punto fijo en la pila en lugar de en relación con los TOS actuales. Normalmente identifica la dirección base del marco de pila actual establecido para el procedimiento actual. Cuando se usa EBP como el registro base en un cálculo de desplazamiento, el desplazamiento se calcula automáticamente en el segmento de pila actual (es decir, el segmento actualmente seleccionado por SS). Como SS no tiene que especificarse explícitamente, la codificación de instrucciones en tales casos es más eficiente. EBP también se puede usar para indexar en segmentos direccionables a través de otros registros de segmentos.
(fuente - http://css.csail.mit.edu/6.858/2017/readings/i386/s02_03.htm )
Dado que en la mayoría de las plataformas de 32 bits, el segmento de datos y el segmento de pila son los mismos, esta asociación de EBP / RBP con la pila ya no es un problema. Lo mismo ocurre con las plataformas de 64 bits: la arquitectura x86-64, introducida por AMD en 2003, ha dejado de admitir en gran medida la segmentación en modo de 64 bits: cuatro de los registros de segmento: CS, SS, DS y ES están obligados a 0 Estas circunstancias de las plataformas x86 de 32 y 64 bits significan esencialmente que el registro EBP / RBP se puede usar, sin ningún prefijo, en las instrucciones del procesador que acceden a la memoria.
Entonces, la opción del compilador sobre la que escribió permite que el BP / EBP / RBP se use para otros medios, por ejemplo, para contener una variable local.
Con "Esto evita las instrucciones para guardar, configurar y restaurar punteros de marco" se quiere evitar el siguiente código en la entrada de cada función:
push ebp
mov ebp, esp
o la instrucción enter
, que fue muy útil en los procesadores Intel 80286 y 80386.
Además, antes de devolver la función, se utiliza el siguiente código:
mov esp, ebp
pop ebp
o la instrucción de leave
.
Las herramientas de depuración pueden escanear los datos de la pila y usar estos datos de registro EBP al localizar call sites
, es decir, mostrar los nombres de la función y los argumentos en el orden en que se les ha llamado jerárquicamente.
Los programadores pueden tener preguntas acerca de los marcos de pila no en un término amplio (que es una sola entidad en la pila que sirve solo una llamada de función y mantiene la dirección de retorno, argumentos y variables locales) pero en un sentido estricto, cuando el término stack frames
es mencionado en el contexto de las opciones del compilador. Desde la perspectiva del compilador, un marco de pila es solo el código de entrada y salida de la rutina , que empuja un anclaje a la pila, que también se puede usar para la depuración y el manejo de excepciones. Las herramientas de depuración pueden escanear los datos de la pila y utilizar estos anclajes para el rastreo retrospectivo, mientras ubican los call sites
en la pila, es decir, para mostrar los nombres de la función en el orden en que se les ha llamado jerárquicamente.
Es por eso que es muy importante entender para un programador qué es un marco de pila en términos de opciones de compilación, porque el compilador puede controlar si genera este código o no.
En algunos casos, el compilador puede omitir el marco de pila (código de entrada y salida para la rutina), y las variables se accederán directamente a través del puntero de pila (SP / ESP / RSP) en lugar del puntero base conveniente (BP / ESP / RSP). Las condiciones para que un compilador omita los marcos de pila para algunas funciones pueden ser diferentes, por ejemplo: (1) la función es una función de hoja (es decir, una entidad final que no llama a otras funciones); (2) no se usan excepciones; (3) no se llaman rutinas con parámetros salientes en la pila; (4) la función no tiene parámetros.
Omitir marcos de pila (código de entrada y salida para la rutina) puede hacer que el código sea más pequeño y más rápido, pero también puede afectar negativamente la capacidad de los depuradores para rastrear los datos en la pila y mostrarlos al programador. Estas son las opciones del compilador que determinan bajo qué condiciones debe cumplir una función para que el compilador la otorgue con la entrada y el código de salida del marco de pila. Por ejemplo, un compilador puede tener opciones para agregar dicho código de entrada y salida a funciones en los siguientes casos: (a) siempre, (b) nunca, (c) cuando sea necesario (especificando las condiciones).
Regresar de las generalidades a las particularidades: si usa la opción del compilador GCC -fomit-frame-pointer
, puede ganar tanto en el código de entrada como de salida para la rutina y en tener un registro adicional (a menos que ya esté activado de manera predeterminada) ya sea por sí mismo o implícitamente por otras opciones, en este caso, ya se está beneficiando de la ganancia de usar el registro EBP / RBP y no se obtendrá ganancia adicional especificando explícitamente esta opción si ya está implícitamente). Sin embargo, tenga en cuenta que en los modos de 16 bits y 32 bits, el registro de BP no tiene la capacidad de acceder a partes de 8 bits como lo tiene AX (AL y AH).
Dado que esta opción, además de permitir que el compilador utilice EBP como un registro de propósito general en optimizaciones, también evita generar código de entrada y salida para el marco de pila que complica la depuración, por eso la documentación de GCC establece explícitamente (resaltando inusualmente con un negrita estilo) que habilitar esta opción hace que la depuración sea imposible en algunas máquinas
Tenga en cuenta también que otras opciones del compilador, relacionadas con la depuración u optimización, pueden activar o desactivar la opción -fomit-frame-pointer
implícitamente.
No encontré ninguna información oficial en gcc.gnu.org sobre cómo afectan otras opciones -fomit-frame-pointer
en plataformas x86 , https://gcc.gnu.org/onlinedocs/gcc-3.4.4/gcc /Optimize-Options.html solo indica lo siguiente:
-O también activa -fomit-frame-pointer en máquinas donde hacerlo no interfiere con la depuración.
Por lo tanto, no está claro en la documentación per se si se -fomit-frame-pointer
si solo se compila con una sola opción -O
en la plataforma x86. Puede probarse empíricamente, pero en este caso los desarrolladores de GCC no se comprometen a no cambiar el comportamiento de esta opción en el futuro sin previo aviso.
Sin embargo, Peter Cordes ha señalado en los comentarios que hay una diferencia para la configuración predeterminada del -fomit-frame-pointer
ficticio entre plataformas x86-16 y plataformas x86-32 / 64.
Esta opción - -fomit-frame-pointer
- también es relevante para el compilador Intel C ++ 15.0 , no solo para el GCC:
Para el Compilador Intel, esta opción tiene un alias /Oy
.
Esto es lo que Intel escribió al respecto:
Estas opciones determinan si EBP se usa como un registro de propósito general en optimizaciones. Las opciones -fomit-frame-pointer y / Oy permiten este uso. Opciones -fno-omit-frame-pointer y / Oy- no lo permite.
Algunos depuradores esperan que EBP se use como un puntero de marco de pila, y no pueden producir una traza inversa de pila a menos que sea así. Las opciones -fno-omit-frame-pointer y / Oy- dirigen al compilador a generar código que mantiene y usa EBP como un puntero de marco de pila para todas las funciones, de modo que un depurador aún puede generar una traza inversa de la pila sin hacer lo siguiente:
Para -fno-omit-frame-pointer: desactivando las optimizaciones con -O0 For / Oy-: desactivando / O1, / O2, / / O3 optimizaciones La opción -fno-omit-frame-pointer se establece cuando se especifica la opción - O0 o la opción -g. La opción -fomit-frame-pointer se establece cuando especifica la opción -O1, -O2 o -O3.
La opción / Oy se establece cuando especifica la opción / O1, / O2 u / O3. Option / Oy- se establece cuando especifica la opción / Od.
El uso de la opción -fno-omit-frame-pointer u / Oy- reduce el número de registros de propósito general disponibles en 1 y puede resultar en un código ligeramente menos eficiente.
NOTA Para sistemas Linux *: Actualmente existe un problema con el manejo de excepciones de GCC 3.2. Por lo tanto, el compilador de Intel ignora esta opción cuando GCC 3.2 está instalado para C ++ y el manejo de excepciones está activado (el valor predeterminado).
Tenga en cuenta que la cita anterior solo es relevante para el compilador Intel C ++ 15, no para GCC.