Estudio de Variables Globales, Locales, Parámetros y Valores de Retorno en C con Lenguaje Ensamblador

Conocimientos Previos

Dirección de la función main llamada por c0s: 11ah Dirección de enlace de la función main: 01fah

I. Variables Globales y Locales

Programa de prueba

int var1, var2, var3;

void funcionA(void);
void funcionB(void);
void funcionC(void);
main()
{
    int localVar1, localVar2, localVar3;
    var1 = 0xa1; var2 = 0xa2; var3 = 0xa3;
    localVar1 = 0xb1; localVar2 = 0xb2; localVar3 = 0xb3;
}

void funcionA(void)
{
    int c1, c2, c3;
    var1 = 0x0fa1; var2 = 0x0fa2; var3 = 0x0fa3;
    c1 = 0xc1; c2 = 0xc2; c3 = 0xc3;
    c1 = c2 + c3;
}

void funcionB(void)
{
    int i = 100;
    while(i--);
}

void funcionC(void)
{
    int h1, h2, h3, h4, h5, h6, h7;
    h1 = 0xc1; h2 = 0xc2; h3 = 0xc3; h4 = 0xc4;
    h5 = 0xc5; h6 = 0xc6; h7 = 0xc7;
    h1 = h2 + h3;
    h2 = h3 + h4;
}

Después de compilar y enlazar, depuramos este código con debug y mostramos el código ensamblador correspondiente para cada función.

1. Función main

(1) Variables Globales

main()
{
    int localVar1, localVar2, localVar3;
    var1 = 0xa1; var2 = 0xa2; var3 = 0xa3;
    localVar1 = 0xb1; localVar2 = 0xb2; localVar3 = 0xb3;
}

Código ensamblador correspondiente

Podemos ver que las variables globales var1, var2, var3 tienen direcciones ds:[01a6], ds:[01a8], ds:[01aa] respectivamente.

La dirección física de ds:[01a6] es 16266h, mientras que la posición final del programa es CS:[2a0] con dirección física 15d60. Por lo tanto, las variables globales están fuera del segmento de código. ds=ss, y sp=ffe6, la posición física de ss:sp es 260a6h, lo que significa que la cima de la pila está en 260a6h, y la pila debería estar por encima de la cima. Por lo tanto, las variables globales no pueden estar en el segmento de pila.

En resumen, creo que las variables globales están fuera del segmento de código y del segmento de pila, y se encuentran en el segmento data (inicializadas) o en el segmento bss (no inicializadas).

(2) Variables Locales

Variables locales asignadas en la pila

a) El compilador primero empuja BP en la pila

b) Usa BP para guardar el puntero de la pila, luego SP-6, para crear espacio para las variables locales.

push bp mov bp,sp sub sp,+6

c) Antes de que la función regrese, restaura la pila y libera el espacio de las variables locales

mov sp,bp

d) Restaura BP

2. Función funcionA

void funcionA(void)
{
    int c1, c2, c3;
    var1 = 0x0fa1; var2 = 0x0fa2; var3 = 0x0fa3;
    c1 = 0xc1; c2 = 0xc2; c3 = 0xc3;
    c1 = c2 + c3;
}

Código ensamblador correspondiente

Las variables locales c1, c2 se asignan en los registros SI, DI, mientras que c3 se asigna en la pila.

Variables locales asignadas en registros

a) El compilador primero empuja BP en la pila

b) Usa BP para guardar el puntero de la pila, empuja SI, DI en la pila, creando espacio para las variables locales

push bp mov bp,sp push si push di

c) Antes de que la función regrese, restaura los registros (libera variables locales) y luego restaura la pila

pop di pop si mov sp,bp

d) Restaura BP

Ya sea asignadas en la pila o en registros, el movimiento del puntero de la pila es el mismo.

3. Función funcionB

void funcionB(void)
{
    int i = 100;
    while(i--);
}

Código ensamblador correspondiente

A través del ensamblador de esta función, podemos verificar repetidamente la conclusión de "variables locales asignadas en registros".

4. Función funcionC

¿Se pueden asignar en otros lugares?

void funcionC(void)
{
    int h1, h2, h3, h4, h5, h6, h7;
    h1 = 0xc1; h2 = 0xc2; h3 = 0xc3; h4 = 0xc4;
    h5 = 0xc5; h6 = 0xc6; h7 = 0xc7;
    h1 = h2 + h3;
    h2 = h3 + h4;
}

Código ensamblador correspondiente

Podemos ver que con 7 variables locales, estas se asignan tanto en la pila como en registros, y no en otro lugar.

¿Cuándo se asignan en la pila y cuándo en registros? Este problema aún no está claro, pero no parece tener mucho significado.

II. Cómo se pasan los parámetros a las funciones y cómo se reciben

Programa de prueba

void mostrarChar(char a, char b);

main()
{
    mostrarChar('a', 2);
}

void mostrarChar(char a, char b)
{
    char r;
    r = a;
    r = b;
}

1. Función main

main()
{
    mostrarChar('a', 2);
}

Código ensamblador correspondiente

a) Los parámetros de la función se pasan a través de la pila, y se empujan de derecha a izquierda secuencialmente

b) Incluso para variables de tipo char, al pasar parámetros, ocupan dos bytes, porque la operación push es de dos bytes. Al tomarlos, se hace según su tipo.

c) La liberación de parámetros se puede lograr mediante múltiples operaciones pop. De hecho, a veces se hace mediante "add sp,+valor". Claramente, este último libera espacio más rápidamente.

2. Función mostrarChar

void mostrarChar(char a, char b)
{
    char r;
    r = a;
    r = b;
}

Código ensamblador correspondiente

d) Antes y después de llamar a la función, la pila debe mantenerse consistente, es decir, cuando la función regrese, el puntero de la pila debe restaurarse al mismo estado que al entrar en la función.

e) La función recibe los parámetros formales tomando valores de la pila.

f) Para las variables locales no inicializadas, el compilador no les asigna un valor inicial, sino que las usa directamente.

g) La vida útil de las variables locales de la función main es más larga que la de los parámetros formales de la función mostrarChar.

h) BP no solo guarda el valor de la pila, sino que a través de él se pueden encontrar los valores de los parámetros pasados. BP+4 es el primer parámetro, BP+6 es el segundo parámetro... Al tomar parámetros, se hace de izquierda a derecha (esta es la maravilla de la pila).

III. Valores de Retorno de Funciones

Programa de prueba

char funcionA(void);

main()
{
    char c;
    c = funcionA();
}

char funcionA(void)
{
    return 'a';
}

1. Función main

char funcionA(void);

main()
{
    char c;
    c = funcionA();
}

Código ensamblador correspondiente

2. Función funcionA

char funcionA(void)
{
    return 'a';
}

Código ensamblador correspondiente

Valores de retorno de función:

Tipo char: AL Tipo int: AX

IV. Resumen

1. Vraiables Globales

Las variables globales están fuera del segmento de código y del segmento de pila, y se encuentran en el segmento data (inicializadas) o en el segmento bss (no inicializadas).

2. Variables Locales

a. Las variables locales asignadas en la pila liberan espacio mediante "mov sp,bp"

b. Las variables locales asignadas en registros (push si/di) se liberan mediante "pop si/di"

c. Las variables locales no inicializadas no son asignadas con un valor inicial por el compilador, sino que se usan directamente.

3. Cómo Pasar Parámetros (Función Principal)

a. Los parámetros de la función se pasan a través de la pila, y se empujan de derecha a izquierda secuencialmente.

b. Incluso para variables de tipo char, al pasar parámetros, ocupan dos bytes porque la operación push es de dos bytes.

c. Al pasar constantes como mostrarChar('a', 2), también se abren dos espacios en la pila, correspondinetes a las dos vraiables de parámetros reales.

4. Cómo Recibir Parámetros (Función Secundaria)

a. La función recibe los parámetros formales tomando valores de la pila.

b. A través de BP se pueden encontrar los valores de los parámetros pasados. BP+4 es el primer parámetro, BP+6 es el segundo parámetro... Al tomar parámetros, se hace de izquierda a derecha.

5. Cómo Liberar Parámetros (Función Principal)

La liberación de parámetros se puede lograr mediante múltiples operaciones pop. De hecho, a veces se hace mediante "add sp,+valor".

6. Valores de Retorno de Funciones

Tipo char: AL Tipo int: AX

V. Otras Conclusiones

1. Variables Locales, Parámetros de Paso y Recepción están Relacionados con la Pila

Una conclusión: las variables locales, el paso de parámetros y la recepción de parámetros están acompañados de operaciones de apilar, desapilar y leer el contenido de la pila. Estas operaciones en la pila completan la creación, uso y liberación de variables y parámetros.

Las variables y parámetros asignados en la pila crean espacio en la pila, y al liberar, se logra moviendo sp o mediante la instrucción pop.

Las variables asignadas en registros obtienen espacio mediante "push si/di", y se liberan mediante "pop si/di".

2. Código Ensamblador de Funciones en C

push bp
mov bp,sp
...
...
mov sp,bp
pop bp
ret

Este código aparece en el ensamblado de cada función, ¿cómo interpretarlo?

a. BP guarda el valor de la pila, por lo que se puede usar la pila para crear espacio para variables locales. (Si no se guarda el valor de la pila, ¿cómo se liberaría este espacio más tarde? Podría ser complicado).

b. A través de BP se pueden encontrar los valores de los parámetros pasados. BP+4 es el primer parámetro, BP+6 es el segundo parámetro... Al tomar parámetros, se hace de izquierda a derecha (esta es la maravilla de la pila).

Referencia: Investigación Integral del Lenguaje Ensamblador de Wang Shang - Cómo las Funciones Reciben Parámetros de Cantidad Indeterminada Investigación Integral del Lenguaje Ensamblador de Wang Shang - Uso del Espacio de Memoria "El Lenguaje Ensamblador", página 319, Experimento de Investigación 3 "Uso del Espacio de Memoria"

Etiquetas: lenguaje ensamblador variables globales variables locales parámetros de función valores de retorno

Publicado el 6-24 04:14