Cómo devolver un valor de retorno compleja?
-
02-10-2019 - |
Pregunta
Actualmente estoy escribiendo algunos procedimientos en lenguaje ensamblador. Como dice alguna convención, cuando quiero volver algún valor a la persona que llama, por ejemplo un entero, que debe devolverlo en el registro EAX. Ahora me pregunto ¿y si quiero devolver un flotador, un doble, una enumeración, o incluso una estructura compleja. Cómo devolver este tipo de valores?
Me puede pensar en volver una dirección en el EAX que apunta al valor real en la memoria. Pero, ¿es la forma estándar?
Muchas gracias ~~~
Solución
Es todo depende de ti, si la persona que llama es su código. Si la persona que llama no está bajo su control, usted tiene que seguir su convención, ya sea existente o desarrollar su propia convención juntos.
Por ejemplo, en la plataforma x86 cuando aritmética de punto flotante es procesada por instrucciones FPU, el resultado de una función se devuelve como el valor superior de la pila de registros FPU. (Si usted sabe, registros x86 FPU están organizados en una "pila circular" de clases). En ese momento no es ni float
ni double
, es un valor almacenado con precisión FPU interna (que podría ser más alta que float
o double
) y es responsabilidad de la persona que llama para recuperar ese valor desde la parte superior de la pila FPU y convertirlo en lo el tipo que desea. De hecho, así es como funciona una instrucción FPU típicos: toma sus argumentos de la parte superior de la pila FPU y empuja la parte trasera resultado en la pila FPU. Mediante la implementación de la función de la misma manera que emula esencialmente una instrucción de FPU "complejo" con su función -. Una manera bastante natural para hacerlo
Cuando aritmética de punto flotante es procesada por instrucciones SSE, la posibilidad de optar SSE registro para el mismo propósito (uso xmm0
al igual que lo utiliza EAX
para enteros).
En estructuras complejas (es decir, los que son más grandes que un registro o un par de registros), la persona que llama normalmente pasar un puntero a un búfer reservado a la función. Y la función sería poner el resultado en la memoria intermedia. En otras palabras, bajo el capó, las funciones nunca "retorno" objetos de gran tamaño, sino que construyen en un búfer de memoria de llamadas proporcionada.
Por supuesto, usted puede utilizar este método de "memoria intermedia" para devolver los valores de cualquier tipo, pero con valores más pequeños, es decir, valores de tipo escalar, es mucho más eficiente utilizar registros de una posición de memoria. Esto se aplica, por cierto, a las estructuras pequeñas también.
Las enumeraciones son por lo general sólo un envoltorio conceptual sobre algún tipo entero. Por lo tanto, no hay diferencia entre el retorno de una enumeración o un número entero.
Otros consejos
Un doble debe ser devuelto cuando el artículo primero de la pila.
Aquí es un C ++ ejemplo de código (x86):
double sqrt(double n)
{
_asm fld n
_asm fsqrt
}
Si prefiere administrar manualmente la pila (ahorro de algunos ciclos de CPU):
double inline __declspec (naked) __fastcall sqrt(double n)
{
_asm fld qword ptr [esp+4]
_asm fsqrt
_asm ret 8
}
Para los tipos de complejos, debe pasar un puntero, o devolver un puntero.
Cuando usted tiene preguntas acerca de convenciones de llamada o lenguaje ensamblador, escribir una función simple en lenguaje de alto nivel (en un archivo separado). A continuación, tiene su compilador generar un lenguaje ensamblador lista o haga que su pantalla depurador "intercalada asamblea".
No sólo la lista le dirá cómo los implementos compilador de código, pero también le mostrará las convenciones de llamada. Mucho más fácil que anuncio a S. O. y por lo general más rápido. ; -)
C99 tiene un complejo de tipo de datos interno (_Complex
). Así que si usted tiene un compilador compatible C99, usted podría compilar una función que devuelve un complejo y compilar este a ensamblador (por lo general con una opción de -S
). Allí se puede ver la convención que se toma.
Depende de la ABI. Por ejemplo, Linux en x86 utiliza el Sys V ABI, especificado en el href="http://refspecs.freestandards.org/elf/abi386-4.pdf" rel="nofollow noreferrer"> Procesador .
La sección función de llamada de secuencia sección tiene la información sobre cómo los valores deben ser devueltos. Brevemente, en este API:
- Funciones regresan escalares o ningún uso valor
%eax
; - Las funciones que devuelven valores de coma flotante utilizan
%st(0)
; - Para las funciones que retornan
struct
ounion
tipos, la persona que llama proporciona espacio para el valor de retorno y pasa su dirección como escondido, el primer argumento. El destinatario de la llamada devuelve esta dirección en%eax
.
Normalmente se usaría la pila
Si usted está planeando hacer interfaz con C u otro lenguaje de alto nivel, por lo general usted aceptaría la dirección de un búfer de memoria como un argumento a su función y devolver su valor complejo poblando ese búfer. Si este es el montaje de solo, entonces se puede definir su propia convención de utilizar cualquier conjunto de registros que desea, aunque por lo general tan solo te hacerlo si tiene una razón específica (por ejemplo, el rendimiento).