¿Por qué se mueve 0 a la pila cuando se usa el valor de retorno?

5 minutos de lectura

Estoy experimentando desmontando clang binarios de programas simples en C (compilados con -O0), y estoy confundido acerca de cierta instrucción que se genera.

Aquí hay dos vacíos main funciones con argumentos estándar, uno de los cuales devuelve valor y otro no:

// return_void.c
void main(int argc, char** argv)
{
}

// return_0.c
int main(int argc, char** argv)
{
    return 0;
}

Ahora, cuando desarmo sus ensamblajes, se ven razonablemente diferentes, pero hay una línea que no entiendo:

return_void.bin:
(__TEXT,__text) section
_main:
0000000000000000    pushq   %rbp
0000000000000001    movq    %rsp, %rbp
0000000000000004    movl    %edi, -0x4(%rbp)
0000000000000007    movq    %rsi, -0x10(%rbp)
000000000000000b    popq    %rbp
000000000000000c    retq

return_0.bin:
(__TEXT,__text) section
_main:
0000000100000f80    pushq   %rbp                
0000000100000f81    movq    %rsp, %rbp          
0000000100000f84    xorl    %eax, %eax          # We return with EAX, so we clean it to return 0
0000000100000f86    movl    $0x0, -0x4(%rbp)    # What does this mean?
0000000100000f8d    movl    %edi, -0x8(%rbp)
0000000100000f90    movq    %rsi, -0x10(%rbp)
0000000100000f94    popq    %rbp
0000000100000f95    retq

Solo se genera cuando uso la función no es nula, así que pensé que podría ser otra forma de devolver 0, pero cuando cambié la constante devuelta, esta línea no cambió en absoluto:

// return_1.c
int main(int argc, char** argv)
{
    return 1;
}

empty_return_1.bin:
(__TEXT,__text) section
_main:
0000000100000f80    pushq   %rbp
0000000100000f81    movq    %rsp, %rbp
0000000100000f84    movl    $0x1, %eax           # Return value modified
0000000100000f89    movl    $0x0, -0x4(%rbp)    # This value is not modified
0000000100000f90    movl    %edi, -0x8(%rbp)
0000000100000f93    movq    %rsi, -0x10(%rbp)
0000000100000f97    popq    %rbp
0000000100000f98    retq

¿Por qué se genera esta línea y cuál es su propósito?

  • ¿Ha probado esto con funciones normales, es decir, funciones que no se llaman principales?

    – Negocio serio

    30 de junio de 2015 a las 22:55

  • main no tener un int el tipo de retorno va en contra del estándar para entornos alojados. No espere ningún comportamiento específico.

    – demasiado honesto para este sitio

    30 de junio de 2015 a las 23:33

  • Prohibiste a clang eliminar código inútil (con -O0). ¿Por qué preguntas sobre un código extraño en el ensamblado?

    – fuz

    30 de junio de 2015 a las 23:41

¿Por que se mueve 0 a la pila cuando se
Hormiga

El propósito de esa área se revela mediante el siguiente código

int main(int argc, char** argv)
{
    if (rand() == 42)
      return 1;

    printf("Helo World!\n");
    return 0;
}

Al principio lo hace

movl    $0, -4(%rbp)

entonces el regreso anticipado queda de la siguiente manera

callq   rand
cmpl    $42, %eax
jne .LBB0_2
movl    $1, -4(%rbp)
jmp .LBB0_3

y luego al final lo hace

.LBB0_3:
movl    -4(%rbp), %eax
addq    $32, %rsp
popq    %rbp
retq

Por lo tanto, esta área está reservada para almacenar el valor de retorno de la función. No parece ser muy necesario y no se usa en código optimizado, pero en -O0 modo que es la forma en que funciona.

clang está haciendo espacio en la pila para los argumentos (registros edi y rsi) y también pone el valor 0 en la pila, por alguna razón. Supongo que clang compila su código en una representación SSA como esta:

int main(int argc, char** argv)
{
    int a;

    a = 0;
    return a;
}

Esto explicaría por qué se asigna una ranura de pila. Si clang también se propaga constantemente, esto explicaría por qué eax se pone a cero en lugar de cargarse desde -4(%rbp). En general, no piense demasiado en construcciones dudosas en no optimizado montaje. Después de todo, le prohibiste al compilador eliminar código inútil.

¿Por que se mueve 0 a la pila cuando se
Giuseppe Pes

movl   $0x0,-0x4(%rbp)

Esta instrucción almacena 0 en %rbp - 4. Parece que clang asigna una variable local oculta para un valor de retorno implícito de main.

De la lista de correo clang:

Si. Asignamos una variable local implícita para contener el valor de retorno; declaraciones de retorno luego simplemente inicialice la ranura de retorno y salte al epílogo, donde se carga y devuelve la ranura. No usamos un phi porque el flujo de control para llegar al epílogo no es necesariamente tan simple como una rama simple, debido a las limpiezas en los ámbitos locales (como los destructores de C++).

Los valores de retorno implícitos como los de main se manejan con un almacén implícito en el prólogo.

Fuente: http://lists.cs.uiuc.edu/pipermail/cfe-dev/2012-February/019767.html

  • eax lo hace tenga el valor de retorno, no estoy seguro de dónde obtuvo esta idea de almacenarlo en la pila, especialmente en el marco de la persona que llama que se destruirá cuando la persona que llama obtenga el control para examinar el valor de retorno. Parece que eres tú quien lo confunde con el regreso. habla a.

    – bufón

    30 de junio de 2015 a las 23:08


  • Además del hecho de que este es un ABI de 32 bits para cualquier sistema, mientras que el OP claramente usa un sistema de 64 bits, el valor de retorno es guardado en EAX/RAX. Estás confundiendo :), tal vez por el nombre Regreso habla ala pregunta OP es realmente buena.

    usuario781847

    30 de junio de 2015 a las 23:08

  • Ambos tienen razón, no lo siento, me equivoqué, estoy actualizando la respuesta. Gracias

    – Giuseppe Pes

    30 de junio de 2015 a las 23:11

  • @Giuseppe Pes: en la plataforma x86, el puntero de la pila se mueve hacia abajo (a direcciones de memoria más bajas) a medida que crece la pila. Esto significa que la dirección de retorno siempre se almacena en positivo compensado desde el rsp valor al inicio de la función. -0x4(%rbp) no puede ser la dirección del remitente en este caso.

    – Ant

    30 de junio de 2015 a las 23:31


  • Es bueno que esté tratando de mejorar su respuesta, pero el hecho es que no es relevante. El OP sabe claramente cómo funcionan las llamadas de pila y función, solo se pregunta (como yo) por qué hay un movimiento con 0 en el cuerpo. Creo que es mejor borrarlo y escribir uno nuevo si sabes la respuesta 🙂

    usuario781847

    30 de junio de 2015 a las 23:32

Según el estándar (para entornos alojados), 5.1.2.2.1, main está obligado a devolver un int resultado. Por lo tanto, no espere un comportamiento definido si viola esto.

Es más, main en realidad _no se requiere que devuelva explícitamente 0; esto se devuelve implícitamente si llega al final de la función. (Tenga en cuenta que esto es sólo para mainque tampoco tiene un prototipo.

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad