Pregunta

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

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

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

En código ensamblador MIPS, esto podría verse así:

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

Como puede ver, no necesité usar la pila en absoluto para n.¿El compilador de C++ lo reconocería y utilizaría directamente los registros de la CPU?

Editar: ¡Vaya, muchas gracias por tus respuestas casi inmediatas y extensas!El cuerpo funcional de foo debería, por supuesto, ser return ++n;, no return n++;. :)

¿Fue útil?

Solución

Renuncia: No sé MIPS, pero sí sé algo de 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 pasar a la función foo. Sin embargo, existe la convención fastcall que se puede usar para decirle a gcc para pasar el valor a través de los registros en su lugar. (MSVC también tiene esta opción, pero no estoy seguro de lo que su sintaxis es.)

test.cpp:

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

La compilación de los anteriores, 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í está foo2:

lea eax,[ecx+0x1]
ret

Ahora toma el valor directamente del registro.

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

Disclaimer 2: No estoy diciendo que usted debe continuamente más allá que el compilador. Probablemente no es práctico y necesario en la mayoría de los casos. Pero no asuma que produce código perfecto.

Editar. 1: Si usted está hablando de las variables locales de fricción (no funcionar argumentos), entonces sí, el compilador asignarlos en los registros o en la pila según lo crea conveniente

Editar 2: Parece que la convención de llamada es de una arquitectura específica, y MIPS pasará los primeros cuatro argumentos en la pila, como Richard Pennington ha declarado en su respuesta. Así, en su caso, usted no tiene que especificar el atributo extra (que en realidad es un atributo específico en x86.)

Otros consejos

Sí. No hay una regla que "las variables siempre se asignan en la pila". El estándar de C ++ no dice nada acerca de un stack.It no asume que existe una pila, o que existen registros. Sólo dice cómo debe comportarse el código, no la forma en que debe aplicarse.

El compilador sólo almacena las variables en la pila cuando se tiene que - cuando tienen que vivir más allá de una llamada de función, por ejemplo, o si se trata de tomar la dirección de ellos

.

El compilador no es estúpida. ;)

Sí, un buen C/C++ optimizador optimizará eso.e incluso MUCHO más: Mira aquí:Encuesta del compilador Felix von Leitners.

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

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

register int x = 10;

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

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 cuatro primeros parámetros, si es pequeño, se pasan en los registros y el valor de retorno se devuelve en un registro. Por lo que 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 que el operador ++:

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

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

Debido a que su función foo ejemplo 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] 

Sí, Los registros se utilizan en C ++. La MDR (registros de datos de memoria) contiene han recuperado y almacenado los datos. Por ejemplo, para recuperar el contenido de la celda 123, tendríamos cargar el valor 123 (en binario) en el MAR y realizar una operación de extracción. Cuando se realiza la operación, una copia del contenido de la celda 123 estaría en la MDR. Para almacenar el valor 98 en la celda 4, cargamos un 4 en el MAR y un 98 en la MDR y realizada de una tienda. Cuando la operación se ha completado el contenido de la celda 4 se han establecido a 98, descartando todo lo que fuera allí anteriormente. Los registros de datos de direcciones y trabajan con ellos para lograrlo. En C ++ también, cuando inicializamos una var con un valor o pedir su valor, el mismo fenómeno sucede.

Y, una cosa más, Moderno compiladores también llevan a cabo Registro de Asignación, que es un poco más rápido que la asignación de memoria.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top