2010-09-13 22 views
5

Actualmente estoy escribiendo algunos procedimientos de lenguaje ensamblador. Como dice alguna convención, cuando quiero devolver algún valor a la persona que llama, digamos un entero, debo devolverlo en el registro EAX. Ahora me pregunto qué pasaría si quiero devolver un float, un double, un enum o incluso una struct compleja. ¿Cómo devolver este tipo de valores?¿Cómo devolver un valor de retorno complejo?

Puedo pensar en devolver una dirección en el EAX que apunta al valor real en la memoria. Pero, ¿es la forma estándar?

Muchas gracias ~~~

+2

Si desea conocer la "forma estándar", debe especificar su plataforma. ¿Qué hardware, qué sistema operativo y qué compilador estás usando? –

+0

@StephenCanon No me di cuenta de que están relacionados cuando publiqué la pregunta. Gracias. – smwikipedia

Respuesta

10

Todo depende de usted, si la persona que llama es su código. Si la persona que llama no está bajo su control, debe seguir su convención existente o desarrollar su propia convención juntos.

Por ejemplo, en la plataforma x86 cuando la aritmética de punto flotante es procesada por instrucciones FPU, el resultado de una función se devuelve como el valor superior en la pila de registro FPU. (Si lo sabe, los registros FPU x86 están organizados en una "pila circular" de géneros). En ese momento no es float ni double, es un valor almacenado con precisión FPU interna (que podría ser mayor que float o double) y es responsabilidad del que llama recuperar ese valor de la parte superior de la pila FPU y convertirlo a cualquier escribe lo desea. De hecho, así es como funciona una instrucción FPU típica: toma sus argumentos de la parte superior de la pila FPU y vuelve a colocar el resultado en la pila FPU. Al implementar su función de la misma manera, esencialmente emula una instrucción FPU "compleja" con su función, una forma bastante natural de hacerlo.

Cuando la aritmética de coma flotante se procesa mediante instrucciones SSE, puede elegir algún registro SSE para el mismo propósito (use xmm0 como usó EAX para enteros).

Para las estructuras complejas (es decir, las 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 pondría el resultado en el búfer. En otras palabras, bajo el capó, las funciones nunca realmente "devuelven" objetos grandes, sino que los construyen en un búfer de memoria provisto por la persona que llama.

Por supuesto, puede utilizar este método de "memoria intermedia" para devolver valores de cualquier tipo, pero con valores más pequeños, es decir, valores de tipo escalar, es mucho más eficiente usar registros que una ubicación de memoria. Esto aplica, por cierto, a estructuras pequeñas también.

Las enumeraciones son generalmente solo un contenedor conceptual sobre algún tipo entero. Entonces, no hay diferencia entre devolver un enum o un entero.

0

Normalmente se usaría la pila

+1

Usando C ABI usa la pila. En C++ no está definido, lo que permite a los fabricantes de compiladores elegir mecanismos más eficientes (si así lo desean). –

0

Si usted está planeando para interactuar 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 para su función y devuelva su valor complejo rellenando ese búfer. Si esto es solo de ensamblaje, entonces puede definir su propia convención usando cualquier conjunto de registros que desee, aunque generalmente solo lo haría si tiene un motivo específico (por ejemplo, rendimiento).

6

Se debe devolver un doble como el 1er artículo en la pila.

Aquí es un C ejemplo ++ código (x86):

double sqrt(double n) 
{ 
    _asm fld n 
    _asm fsqrt 
} 

Si prefiere administrar la pila de forma manual (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.

+0

Si dice que también necesita especificar el compilador (con la versión) y el sistema operativo (con la versión), probablemente debería especificar el documento ABI para su compilador. Esto se debe a que no todos los compiladores C++ hacen eso. Todo el compilador C lo hará porque el C ABI está bien definido. –

+0

Derecha. es para Win32, VC++ 2008. Pero probablemente funcione para todas las versiones de VC. –

1

C99 tiene un tipo de datos incorporado complejo (_Complex). Entonces, si tiene un compilador compatible con C99, puede compilar alguna función que devuelva un complejo y compilar esto para el ensamblador (generalmente con una opción -S). Ahí puedes ver la convención que se toma.

+0

también intente con '-save-temps'. –

2

Cuando tenga preguntas sobre las convenciones de llamada o el lenguaje ensamblador, escriba una función simple en un lenguaje de alto nivel (en un archivo separado). Luego, haga que su compilador genere una lista en lenguaje ensamblador o haga que su depurador muestre "ensamblado intercalado".

La lista no solo le indicará cómo el compilador implementa el código, sino que también le mostrará las convenciones de las llamadas. Mucho más fácil que publicar en S.O. y generalmente más rápido. ;-)

1

Depende del ABI. Por ejemplo, Linux en x86 usa Sys V ABI, especificado en el Intel386 Architecture Processor Supplment, Fourth Edition.

La sección Función que llama a la Secuencia tiene la información sobre cómo se deben devolver los valores. En resumen, en esta API:

  • Funciones que devuelven escalares o ningún uso de valor %eax;
  • Las funciones que devuelven valores de coma flotante usan %st(0);
  • Para las funciones que devuelven struct o union tipos, la persona que llama proporciona espacio para el valor de retorno y pasa su dirección como un primer argumento oculto. El destinatario devuelve esta dirección en %eax.
Cuestiones relacionadas