2009-12-02 34 views
10

En C++, las variables locales siempre se asignan en la pila. La pila es una parte de la memoria permitida que su aplicación puede ocupar. Esa memoria se guarda en su RAM (si no se intercambia en el disco). Ahora, ¿un compilador C++ siempre crea un código ensamblador que almacena variables locales en la pila?C++ CPU Registrar uso

Tomemos, por ejemplo, el siguiente código simple:

int foo(int n) { 
    return ++n; 
} 

En el código ensamblador MIPS, esto podría tener este aspecto:

foo: 
addi $v0, $a0, 1 
jr $ra 

Como se puede ver, no tenía necesidad de usa la pila en absoluto para n. ¿Lo reconocería el compilador de C++ y usaría directamente los registros de la CPU?

Edit: ¡Guau, muchas gracias por sus respuestas casi inmediatas y extensas! El cuerpo de la función de foo debería ser, por supuesto, return ++n;, no return n++;. :)

+0

El compilador optimizaría. Pruebe 'gcc -fverbose-asm -O2 -S yoursource.c' luego mire dentro de' yoursource.s' –

Respuesta

9

de responsabilidad: No sé MIPS, pero conozco algunos x86, y creo que el principio debe ser el mismo ..

En la convención de llamada de función habitual, el compilador empujará el valor de n en la pila para pasarlo a la función foo. Sin embargo, existe la convención fastcall que puede usar para decirle a gcc que pase el valor a través de los registros en su lugar. (MSVC también tiene esta opción, pero no estoy seguro de cuál es su sintaxis.)

test.cpp:

int foo1 (int n) { return ++n; } 
int foo2 (int n) __attribute__((fastcall)); 
int foo2 (int n) { 
    return ++n; 
} 

Compilación de lo anterior con g++ -O3 -fomit-frame-pointer -c test.cpp, me pasa por foo1:

mov eax,DWORD PTR [esp+0x4] 
add eax,0x1 
ret 

Como se puede ver, se lee en el valor de la pila.

Y aquí es foo2:

lea eax,[ecx+0x1] 
ret 

Ahora toma el valor directamente del registro.

Por supuesto, si incorpora la función, el compilador hará una simple adición en el cuerpo de su función más grande, independientemente de la convención de llamadas que especifique. Pero cuando no se puede incluir en línea, esto va a suceder.

Descargo de responsabilidad 2: No estoy diciendo que deba adivinar continuamente el compilador. Probablemente no sea práctico y necesario en la mayoría de los casos. Pero no suponga que produce un código perfecto.

Edit 1: Si habla de variables locales simples (no argumentos de funciones), entonces sí, el compilador las asignará en los registros o en la pila como lo considere oportuno.

Editar 2: Parece que la convención de llamadas es específica de la arquitectura, y MIPS pasará los primeros cuatro argumentos en la pila, como Richard Pennington ha declarado en su respuesta. Por lo tanto, en su caso, no tiene que especificar el atributo adicional (que es, de hecho, un atributo específico de x86).

+1

-O deshabilita la configuración del marco de la pila en las máquinas donde no interfiere con la depuración - x86 isn 't uno de ellos, necesita un puntero de marco de fomit separado para eliminar la configuración de marco de pila' redundante '(que es realmente útil para la depuración, es decir, en el desenrollado de la estructura de la pila) – matja

+0

Sí, me olvidé por completo de eso. Lo arreglaré. Pero la diferencia permanece. – int3

+0

Un compilador que optimiza el tiempo de enlace también puede reconocer que una llamada puede convertirse en una llamada rápida por sí sola, ya que puede ver y arreglar todos los sitios de llamadas. –

12

Sí. No existe una regla según la cual "las variables siempre se asignan en la pila". El estándar C++ no dice nada sobre una pila. No supone que exista una pila, o que existan registros. Simplemente dice cómo debe comportarse el código, no cómo debe implementarse.

El compilador solo almacena variables en la pila cuando tiene que - cuando tienen que vivir más allá de una llamada de función por ejemplo, o si intenta tomar la dirección de ellas.

El compilador no es estúpido. ;)

8

Sí, una buena optimización de C/C++ optimizará eso. E incluso MUCHO más: See here: Felix von Leitners Compiler Survey.

Un compilador C/C++ normal no pondrá todas las variables en la pila de todos modos. El problema con su función foo() podría ser que la variable podría pasarse a través de la pila a la función (el ABI de su sistema (hardware/OS) lo define).

Con la palabra clave register de C puede darle al compilador una sugerencia de que probablemente sería bueno almacenar una variable en un registro. Muestra:

register int x = 10; 

Pero recuerde: El compilador es libre de no almacenar x en un registro si quiere!

+0

+1 para el gran enlace –

6

La respuesta es sí, tal vez. Depende del compilador, el nivel de optimización y el procesador de destino.

En el caso de los mips, los primeros cuatro parámetros, si son pequeños, se pasan en registros y el valor devuelto se devuelve en un registro. Por lo tanto, su ejemplo no tiene ningún requisito para asignar nada en la pila.

En realidad, la verdad es más extraña que la ficción. En su caso, el parámetro se devuelve sin cambios: el valor devuelto es el de n antes de la ++ operador:

foo: 
    .frame $sp,0,$ra 
    .mask 0x00000000,0 
    .fmask 0x00000000,0 

    addu $2, $zero, $4 
    jr  $ra 
    nop 
2

Debido a que su ejemplo foo función es una función de identidad (simplemente devuelve su argumento), mi compilador de C++ (VS 2008) elimina por completo esta llamada de función. Si lo cambio a:

int foo(int n) { 
    return ++n; 
} 

los inlines compilador esto con

lea edx, [eax+1] 
+0

Sí, de nuevo con un ejemplo de mips: static int foo (int n) { return n ++; } int fee() { return foo (5); } da: .text .Align 2 tarifa .globl tarifa .ENT retraso: .Frame $ sp, 0, $ ra .mask 0x00000000,0 .fmask 0x00000000,0 addiu $ 2 , $ cero, 5 jr $ ra nop .set macro .set reordenar .end tarifa .size cuota,.-tarifa –

0

Sí, Los registros se utilizan en C++. El MDR (registros de datos de memoria) contiene los datos que se buscan y almacenan. Por ejemplo, para recuperar el contenido de la celda 123, cargaríamos el valor 123 (en binario) en el MAR y realizaríamos una operación de búsqueda. Cuando finalice la operación, una copia del contenido de la celda 123 estará en el MDR. Para almacenar el valor 98 en la celda 4, cargamos un 4 en el MAR y un 98 en el MDR y realizamos una tienda. Cuando se completa la operación, el contenido de la celda 4 se habrá establecido en 98, descartando lo que haya antes. Los registros de direcciones & de datos trabajan con ellos para lograr esto.También en C++, cuando inicializamos una var con un valor o preguntamos su valor, ocurre el mismo fenómeno.

Y, una cosa más, los compiladores modernos también realizan asignación de registro, que es un poco más rápido que la asignación de memoria.