Lee un vector de enteros, calcula su suma y su máximo con bucles en ensamblador x86-64 (sintaxis Intel) y muestra el resultado llamando a scanf y printf.
Actualizado el
Código de Examen Ensamblador Fundamentos de programación Nivel examen Código probado

Una placa de control registra, una vez por minuto, la variación de consumo de una máquina en vatios. Cada lectura es un entero con signo: un valor positivo indica que el consumo ha subido y uno negativo, que ha bajado. Queremos un pequeño programa en ensamblador x86-64 (GNU as, .intel_syntax noprefix) que guarde las lecturas en un vector y después las analice.
Entrada (por la entrada estándar): un entero n con 0 ≤ n ≤ 1000 y, a continuación, n enteros separados por espacios o saltos de línea. Cada uno está en el intervalo [-1000000000, 1000000000].
Salida: si n vale 0, el programa imprime la línea Vector vacio. En otro caso imprime dos líneas: Suma: S, donde S es la suma de todas las lecturas, y Maximo: M (posicion P), donde M es la mayor lectura y P su índice, contado desde 0. Si el máximo aparece varias veces, P es el índice de la primera aparición. Las palabras van sin tilde y cada línea termina con un salto de línea.
Requisitos: el programa define la función main en un único fichero main.s y se enlaza con gcc -no-pie. La lectura se hace con scanf y la escritura con printf. Los elementos se almacenan primero en un vector de 1000 enteros de 64 bits reservado en .bss. Después se recorren con un segundo bucle que calcula la suma y el máximo. Hay que respetar la convención de llamada System V AMD64, incluida la alineación de la pila a 16 bytes en cada call.
Debajo se muestra un ejemplo de entrada con su salida correspondiente.
Entrada de ejemplo
6
4 -2 9 3 9 -5Salida esperada
Suma: 18
Maximo: 9 (posicion 2)- Lectura de n y de los n elementos en el vector con scanf, pasando la dirección correcta de cada casilla (escala 8).
- 0,75
- Bucle de recorrido: suma acumulada en 64 bits, máximo con comparación con signo y posición de la primera aparición.
- 1,0
- Respeto de la convención de llamada System V: argumentos en rdi, rsi y rdx, eax a cero, pila alineada a 16 bytes y registros callee-saved preservados.
- 0,5
- Tratamiento del vector vacío y formato de salida exacto.
- 0,25
Pistas
Pista 1 · ¿Qué dirección recibe scanf en cada vuelta?
scanf("%ld", p) necesita en rsi la dirección donde escribir, no un valor. Si cada elemento ocupa 8 bytes, el elemento i está en base + i*8. El modo de direccionamiento [base + índice*8] combinado con lea calcula esa dirección sin multiplicar a mano.
Pista 2 · ¿Qué registros sobreviven a una llamada?
scanf y printf pueden destruir rax, rcx, rdx, rsi, rdi y r8–r11. El contador del bucle de lectura y el valor de n deben vivir en registros que la función llamada está obligada a conservar: rbx, r12–r15. Si los usas, guárdalos con push al entrar en main y cuenta cuántos bytes ocupa la pila después.
Pista 3 · ¿Con qué valor empieza el máximo?
Inicializar el máximo a 0 falla cuando todas las lecturas son negativas. Tomar el primer elemento como suma y máximo iniciales, con posición 0, y recorrer desde i = 1 lo resuelve. Para quedarte con la primera aparición, actualiza solo cuando el nuevo valor sea estrictamente mayor. Usa además un salto con signo (jle/jg), no jbe/ja.
Solución
Solución explicada
La solución tiene tres partes: un prólogo que guarda registros y deja la pila alineada, un bucle que lee los n elementos en vec, y un bucle que recorre el vector acumulando la suma y buscando el máximo. El caso n = 0 se desvía al principio a la etiqueta vacio.
Los valores que deben sobrevivir a las llamadas a libc van en registros callee-saved: rbx guarda n, r12 el índice de lectura y luego la suma, r13 el máximo y r14 la base del vector y luego la posición del máximo.
.intel_syntax noprefix
.section .rodata
fmt_in: .string "%ld"
fmt_suma: .string "Suma: %ld\n"
fmt_max: .string "Maximo: %ld (posicion %ld)\n"
msg_vacio: .string "Vector vacio\n"
.bss
.align 8
n: .skip 8 # numero de elementos
vec: .skip 8000 # 1000 enteros de 64 bits
.text
.globl main
main:
push rbp # rsp queda alineado a 16
mov rbp, rsp
push rbx # 4 pushes de 8 bytes = 32 bytes:
push r12 # la alineacion a 16 se mantiene
push r13
push r14
# scanf("%ld", &n)
lea rdi, [rip + fmt_in]
lea rsi, [rip + n]
xor eax, eax
call scanf
cmp eax, 1
jne vacio
mov rbx, [rip + n] # rbx = n
test rbx, rbx
jle vacio
# Bucle de lectura: for (i = 0; i < n; i++) scanf("%ld", &vec[i])
xor r12d, r12d # r12 = i = 0
leer:
cmp r12, rbx
jge fin_leer
lea rdi, [rip + fmt_in]
lea rax, [rip + vec]
lea rsi, [rax + r12*8] # rsi = &vec[i]
xor eax, eax
call scanf
inc r12
jmp leer
fin_leer:
# Bucle de recorrido: suma y maximo
lea r14, [rip + vec] # r14 = &vec[0]
mov r12, [r14] # suma = vec[0]
mov r13, r12 # maximo = vec[0]
xor edx, edx # posmax = 0
mov ecx, 1 # i = 1
recorrer:
cmp rcx, rbx
jge fin_recorrer
mov rax, [r14 + rcx*8] # rax = vec[i]
add r12, rax # suma += vec[i]
cmp rax, r13
jle no_max # con signo; estricto: primera aparicion
mov r13, rax # maximo = vec[i]
mov rdx, rcx # posmax = i
no_max:
inc rcx
jmp recorrer
fin_recorrer:
mov r14, rdx # posmax a un registro que sobrevive a printf
# printf("Suma: %ld\n", suma)
lea rdi, [rip + fmt_suma]
mov rsi, r12
xor eax, eax
call printf
# printf("Maximo: %ld (posicion %ld)\n", maximo, posmax)
lea rdi, [rip + fmt_max]
mov rsi, r13
mov rdx, r14
xor eax, eax
call printf
jmp salir
vacio:
lea rdi, [rip + msg_vacio]
xor eax, eax
call printf
salir:
xor eax, eax # return 0
pop r14
pop r13
pop r12
pop rbx
pop rbp
ret
.section .note.GNU-stack,"",@progbitsDatos. En .rodata están las cadenas de formato. fmt_in es "%ld" porque cada elemento es un long de 8 bytes. En .bss se reservan n (8 bytes) y vec (1000 × 8 = 8000 bytes), que el sistema inicializa a cero sin ocupar espacio en el ejecutable. La directiva .align 8 garantiza que cada elemento empiece en una dirección múltiplo de 8.
Prólogo y alineación. Al entrar en main, el call que nos invocó ha apilado la dirección de retorno, así que rsp vale 8 módulo 16. Con push rbp pasa a 0 módulo 16. Los cuatro push siguientes suman 32 bytes y mantienen esa alineación, de modo que todas las llamadas posteriores se hacen con la pila alineada sin necesidad de sub rsp. Esos cuatro registros se apilan porque la ABI obliga a main a devolverlos intactos.
Lectura de n. Se pasa el formato en rdi y la dirección de n en rsi, calculada con lea relativo a rip. xor eax, eax indica a la función variádica que no se pasan argumentos en registros vectoriales. Si scanf no devuelve 1 (entrada vacía) o si n ≤ 0, el flujo salta a vacio. Así se cubre el test «vector vacío», que imprime Vector vacio.
Bucle leer. Es un for con la condición al principio: cmp r12, rbx y jge fin_leer. La instrucción clave es lea rsi, [rax + r12*8], que calcula &vec[i] sin acceder a memoria. El índice está en r12 y n en rbx, y ambos sobreviven a las sucesivas llamadas a scanf. Si se usara rcx como contador, cualquier llamada podría destruirlo.
Bucle recorrer. La suma y el máximo empiezan en vec[0], la posición en 0 y el índice en 1. En cada vuelta se carga vec[i] en rax y se acumula en r12. Después se compara con el máximo en r13. El salto jle no_max es con signo y deja pasar solo los valores estrictamente mayores. Por eso, en el ejemplo, donde el 9 aparece en las posiciones 2 y 4, se informa la posición 2. Aquí sí se pueden usar rcx y rdx, porque dentro del bucle no hay ninguna llamada.
Escritura. Antes del primer printf, la posición se copia de rdx a r14, ya que printf puede machacar rdx. La base del vector ya no hace falta, así que r14 queda libre. Luego se hacen dos llamadas con los argumentos en rdi, rsi y rdx, siempre en el orden de la ABI. El epílogo restaura los registros en orden inverso y devuelve 0 en eax.
Casos de prueba. El test con todos los valores negativos (máximo −3) comprueba que no se ha inicializado el máximo a 0. El de un solo elemento comprueba que el bucle de recorrido no ejecuta ninguna vuelta y aun así se imprime vec[0]. El de valores grandes produce una suma de 3999999999, que no cabe en 32 bits con signo: confirma que se usan registros de 64 bits y el formato %ld. El último caso sitúa el máximo en la posición 4 para comprobar que se procesa el último elemento.
Casos de prueba
| Caso | Entrada | Salida esperada | Salida real | Resultado |
|---|---|---|---|---|
| ejemplo con máximo repetido | 6
4 -2 9 3 9 -5 | Suma: 18
Maximo: 9 (posicion 2) | Suma: 18
Maximo: 9 (posicion 2) | OK |
| vector vacío | 0 | Vector vacio | Vector vacio | OK |
| un solo elemento negativo | 1
-7 | Suma: -7
Maximo: -7 (posicion 0) | Suma: -7
Maximo: -7 (posicion 0) | OK |
| todos negativos | 4
-8 -3 -3 -10 | Suma: -24
Maximo: -3 (posicion 1) | Suma: -24
Maximo: -3 (posicion 1) | OK |
| suma que no cabe en 32 bits | 4
1000000000 1000000000 1000000000 999999999 | Suma: 3999999999
Maximo: 1000000000 (posicion 0) | Suma: 3999999999
Maximo: 1000000000 (posicion 0) | OK |
| máximo en la última posición | 5
0 0 0 0 1 | Suma: 1
Maximo: 1 (posicion 4) | Suma: 1
Maximo: 1 (posicion 4) | OK |
Salidas reales: código compilado con GNU as 2.44 (x86-64) y ejecutado en un contenedor aislado el 28 de octubre de 2026.
Complejidad
Tiempo: Θ(n). El bucle leer hace exactamente n llamadas a scanf. El bucle recorrer ejecuta n − 1 vueltas con un número constante de instrucciones cada una: una carga, una suma, una comparación y, como mucho, dos mov. En la práctica, el coste real lo dominan las llamadas a scanf, mucho más caras que el recorrido.
Memoria: 8 KB fijos en .bss para el vector, más 8 bytes para n y 48 bytes de pila (dirección de retorno y cinco registros apilados). Es O(1) respecto al tamaño de la entrada porque el vector se dimensiona para el máximo permitido. Si se quisiera calcular solo la suma y el máximo bastaría con no guardar los datos, pero el enunciado exige almacenarlos primero en el vector.
Errores típicos
- Pasar a
scanfel contenido de la casilla (mov rsi, [vec + i*8]) en lugar de su dirección (lea). El programa escribe en una dirección arbitraria y suele terminar con un fallo de segmentación. - Usar escala 4 o formato
%dcon un vector de 8 bytes por elemento. Los valores se solapan o quedan con la mitad alta sin inicializar, y la suma de valores grandes desborda 32 bits. - Guardar el contador del bucle de lectura en
rcxorsi. Son registros caller-saved yscanfpuede modificarlos, lo que produce bucles infinitos o lecturas que se saltan elementos. - Comparar el máximo con
ja/jbe(sin signo). Así, −5 se considera mayor que 9, porque su representación en complemento a dos es un número sin signo enorme. - Inicializar el máximo a 0. Falla cuando todas las lecturas son negativas, y también la posición resultante es incorrecta.
- Hacer un número impar de
pushtraspush rbpsin compensar consub rsp, 8. La pila queda desalineada en elcall, yprintfoscanfpueden abortar en instrucciones SSE que exigen alineación.
Variantes
Añadir el mínimo y su posición en el mismo recorrido
Basta con dos registros más que no se usen en el bucle, por ejemplo r8 para el mínimo y r9 para su posición, ambos inicializados con vec[0] y 0. En cada vuelta se añade cmp rax, r8 seguido de jge no_min. Antes de las llamadas a printf hay que copiarlos a registros callee-saved, lo que obliga a apilar r15. Con cinco push tras rbp, la pila queda desalineada: se corrige con un sub rsp, 8 en el prólogo y el add rsp, 8 correspondiente en el epílogo.
Vector de enteros de 32 bits con suma en 64 bits
Si el vector se declara de int (4 bytes), la lectura usa "%d" y la dirección es [base + i*4]. La carga debe extender el signo a 64 bits con movsxd rax, dword ptr [r14 + rcx*4] antes de sumar en r12. Un simple mov eax, [...] extiende con ceros y convierte los negativos en positivos grandes. Para imprimir la suma se mantiene %ld.
En los temarios
El recorrido de un vector mediante una dirección base y un índice es la versión a bajo nivel de lo que se estudia como arrays y punteros. Esa unidad figura en el programa de Algoritmos y Programación I (95.11) de la Universidad de Buenos Aires, que incluye arrays, arrays multidimensionales, la relación entre arrays y punteros y los operadores de punteros. Ver en ensamblador que &vec[i] no es más que base + i*8 ayuda a fijar esos conceptos. Si quieres repasar por qué scanf necesita una dirección, te puede servir el ejercicio de paso por valor y por referencia en C.
La interacción directa con la biblioteca del sistema y con el sistema operativo aparece en el programa de CC3301 Programación de Software de Sistemas de la Universidad de Chile, que trata la API de Linux para archivos y directorios, los procesos con fork, exec y pipes, el intérprete de comandos y las señales. Este ejercicio se queda un nivel por debajo, en la convención de llamada que hace posible invocar esas funciones desde código máquina.
Historial de cambios
- Reescrito con la plantilla de Código de Examen: enunciado de examen, pistas, solución explicada, casos de prueba y código compilado y ejecutado; versión en inglés enlazada.
- Publicación original.
Artículo generado mediante AI.larebelion · Revisado por Kernel


