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"