2010-12-09 19 views
9

Quiero saber cómo funciona el pasar argumentos a las funciones en C. ¿Dónde se almacenan los valores y cómo y cómo se recuperaron? ¿Cómo funciona el argumento variado? Además, ya que está relacionado: ¿qué pasa con los valores de retorno?¿Cómo funciona el argumento que pasa?

Tengo un conocimiento básico de los registros de la CPU y el ensamblador, pero no lo suficiente como para comprender completamente el ASM que GCC me escupe. Algunos ejemplos anotados simples serían muy apreciados.

+2

La transferencia de argumentos depende de la convención de llamadas, que depende de la CPU. ¿Qué CPU estás usando? MIPS? x86? x86-64? – Gabe

+1

posible duplicado de [¿Cuáles son las diferentes convenciones de llamadas en C/C++ y qué significan cada una?] (Http://stackoverflow.com/questions/949862/what-are-the-different-calling-conventions-in-cc -and-what-do-each-mean) –

+0

@Gabe x86. cdecl y stdcall son probablemente más pertinentes. @Ignacio Vazquez-Abrams buen enlace, leyendo algunas de las cosas que hay ahora –

Respuesta

3

Sus preguntas son más de las que cualquiera podría razonablemente tratar de responder en una publicación de SO, sin mencionar que su implementación también está definida.

Sin embargo, si está interesado en la respuesta x86, le sugiero que vea esta conferencia de Stanford CS107 titulada Programming Paradigms donde todas las respuestas a las preguntas planteadas se explicarán en gran detalle (y bastante elocuentemente) en los primeros 6- 8 conferencias

+0

Estas conferencias se ven bien. Definitivamente voy a intentar echarles un vistazo. Solo necesito encontrar el tiempo :) –

4

Es específico de la plataforma y es parte del "ABI". De hecho, algunos compiladores incluso le permiten elegir entre diferentes convenciones.

Microsoft Visual Studio, por ejemplo, ofrece la convención de llamadas __fastcall llamando, que usa registros. Otras plataformas o convenciones de llamadas usan la pila exclusivamente.

Los argumentos variables funcionan de una manera muy similar: se pasan a través de registros o pila. En el caso de los registros, generalmente están en orden ascendente, según el tipo. Si tiene algo como (int a, int b, float c, int d), un PowerPC ABI podría poner a en r3, b en r4, d en r5, y c en fp1 (Olvidé dónde comienzan los registros de flotación, pero captar la idea).

Los valores de retorno, de nuevo, funcionan de la misma manera.

Lamentablemente, no tengo muchos ejemplos, la mayor parte de mi ensamblaje está en PowerPC, y todo lo que ve en el ensamblaje es el código que va directamente a r3, r4, r5 y también coloca el valor de retorno en r3 .

0

Básicamente, C pasa argumentos presionándolos en la pila. Para los tipos de puntero, el puntero se empuja en la pila.

Una cosa sobre C es que la persona que llama restaura la pila en lugar de llamar a la función. De esta forma, la cantidad de argumentos puede variar y la función llamada no necesita saber con anticipación cuántos argumentos se pasarán.

Los valores devueltos se devuelven en el registro AX, o variaciones de los mismos.

+0

Aunque lo que describes es una ocurrencia común de cómo funcionan las cosas, en ninguna parte del estándar C se describe el concepto de 'pila'. Todas las cosas que mencionas son detalles definidos de implementación y no están vinculadas a C de ninguna manera. – SiegeX

+0

@SiegeX: Creo que esta respuesta es una suposición razonable, teniendo en cuenta que el OP no indicó la arquitectura que están utilizando. – wj32

+0

Ciertamente no es cierto que C siempre use la restauración de llamadas. * Se requiere * para funciones de argumento variable, pero no para aquellas con parámetros fijos. Por lo tanto, el restablecimiento de llamada se usa a menudo en este último caso. Un ejemplo común es [fastcall] (http://en.wikipedia.org/wiki/X86_calling_conventions#fastcall). –

15

Teniendo en cuenta este código:

int foo (int a, int b) { 
    return a + b; 
} 

int main (void) { 
    foo(3, 5); 
    return 0; 
} 

Compilarlo con gcc foo.c -S da la salida de montaje:

foo: 
    pushl %ebp 
    movl %esp, %ebp 
    movl 12(%ebp), %eax 
    movl 8(%ebp), %edx 
    leal (%edx,%eax), %eax 
    popl %ebp 
    ret 

main: 
    pushl %ebp 
    movl %esp, %ebp 
    subl $8, %esp 
    movl $5, 4(%esp) 
    movl $3, (%esp) 
    call foo 
    movl $0, %eax 
    leave 
    ret 

Así que, básicamente, la persona que llama (en este caso main) primero asigna 8 bytes en la pila para acomodar los dos argumentos, luego coloca los dos argumentos en la pila en los desplazamientos correspondientes (4 y 0), y luego se emite la instrucción call que transfiere t él controla a la rutina foo. La rutina foo lee sus argumentos de los desplazamientos correspondientes en la pila, los restaura y coloca su valor de retorno en el registro eax para que esté disponible para la persona que llama.

+0

+1 simple y directo, exactamente lo que pidió :) – BlackBear

Cuestiones relacionadas