Unidad 2. De lenguaje de alto nivel a código ejecutable
Introducción
En este punto del curso ya tienes una buena idea de cómo funciona un computador. También escribiste algunos programas en lenguaje ensamblador y estudiaste la relación entre este y el lenguaje de máquina.
A partir de ahora vas a emprender un viaje por varios de los componentes de software relacionados con los sistemas de cómputo. En esta unidad vas a aprender un nuevo lenguaje de programación muy relacionado con la programación a nivel de sistema.
Propósito de aprendizaje
Aprender un nuevo lenguaje de programación que te acerque a los conceptos de los sistemas de cómputo más naturalmente: lenguaje C.
Temas
Introducción al lenguaje C
Trayecto de actividades
Ejercicios
Ejercicio 1: lenguaje de programación C
Este será uno de los ejercicios más largos de la unidad porque vamos a introducir
el lenguaje de programación con el cual estudiaremos los conceptos que nos quedan del curso: lenguaje C.
- Introducción a C
- Prerrequisitos
- Temas
- Lecturas, ejercicios y retos
- Lectura 1: ¿Qué es C y sus características?
- Lectura 2: Estructura básica de un programa en c
- Ejercicio 1: estructura de un programa en C
- Ejercicio 2: compilación de un programa en C
- Lectura 3: El preprocesador
- Lectura 4: Tipos de datos, tamaños y rangos
- Lectura 5: Variables
- Lectura 6: Ámbito y accesibilidad de las variables
- Ejercicio 3: ámbito y accesibilidad de variables
- Ejercicio 4: Operadores
- Lectura 7: Instrucciones de control de flujo
- Lectura 8: Punteros
- Reto 1: argumentos, punteros y funciones
- Lectura 9: Arreglos y su relación con los punteros
- Reto 2: arreglos, funciones, punteros
- Lectura 10: cadenas de caracteres
- Reto 3: arreglos, cadenas, punteros
- Lectura 11: memoria dinámica
- Lectura 12: estructuras de datos
- Reto 4: comparación de estructuras
- Lectura 13: estructuras y punteros
- Lectura 14: entrada/salida (teclado-pantalla)
- Ejercicio 5: ingresar números por teclado
- Ejercicio 6: convertir caracteres a números (forma 1)
- Ejercicio 7: convertir caracteres a números (forma 2)
- Ejercicio 8: imprimir con printf
- Lectura 15: entrada/salida (archivos)
- Ejercicio 9: leer un archivo hasta el final
- Reto 5: leer y escribir en un archivo
- Ejercicio 10: repaso de punteros
- Ejercicio 11: arreglos de arreglos
- Ejercicio 12: repaso de arreglos, punteros y funciones
- Reto 6: funciones, arreglos y punteros
- Ejercicio 13: repaso de archivos
Ejercicio 2: de lenguaje de alto nivel a código ejecutable
¿Cómo llegamos del código fuente al binario (el ejecutable)?
En el caso del lenguaje C se siguen unos pasos conocidos como el pipeline de compilación compuesto por 4 pasos: preprocesamiento, compilación, ensamblado y enlazado.
IMPORTANTE: para poder conseguir un ejecutable desde el código fuente, es necesario que nuestro código pase por todas las etapas del pipeline de manera exitosa.
Para ilustrar el proceso vamos a crear un programa compuesto por 3 archivos: dos archivos .c y un archivo .h. Todos los archivos estarán almacenados en el mismo directorio.
min.h
#ifndef MIN_H
#define MIN_H
int min(int, int);
#endif
min.c
#include "min.h"
int min(int a, int b){
if(a < b) return a;
else return b;
}
main.c
#include "min.h"
#include <stdio.h>
int main(int argc, char* argv[]){
printf("the min value is: %d\n",min(1,2));
return 0;
}
La idea será crear un ejecutable partiendo de estos tres archivos.
Ten presente que los archivos .h se usan para informarle al compilador
qué tipo de datos recibe la función min y qué tipo de dato devuelve. Los
archivos .h no se compilan, solo los archivos .c.
Compilamos primero min.c:
Preprocesamiento:
gcc -E min.c. Al ejecutar este comando nota como el preprocesador incluye la información de min.h a min.cCompilación: ejecuta el comando
gcc -S min.c. La opción-Sindica quegccdebe hacer el proceso de preprocesador y con la salida de este paso se alimenta al compilador y detenerse en ese punto. El archivo de salida generado serámin.sque contendrá el código ensamblador.
.file "min.c"
.text
.globl min
.type min, @function
min:
.LFB0:
.cfi_startproc
endbr64
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
movl %edi, -4(%rbp)
movl %esi, -8(%rbp)
movl -4(%rbp), %eax
cmpl -8(%rbp), %eax
jge .L2
movl -4(%rbp), %eax
jmp .L3
.L2:
movl -8(%rbp), %eax
.L3:
popq %rbp
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE0:
.size min, .-min
.ident "GCC: (Ubuntu 9.3.0-10ubuntu2) 9.3.0"
.section .note.GNU-stack,"",@progbits
.section .note.gnu.property,"a"
.align 8
.long 1f - 0f
.long 4f - 1f
.long 5
0:
.string "GNU"
1:
.align 8
.long 0xc0000002
.long 3f - 2f
2:
.long 0x3
3:
.align 8
4:
Ensamblado: en esta fase se genera el código máquina.
as min.s -o min.o. También es posible generar el código de máquina con el comandogcc -c min.cDebemos repetir este proceso con todos los archivos
.cde nuestro proyecto:gcc -c main.c. Ten presente que el comando anterior ejecutará automáticamente todos los pasos previos, es decir, el preprocesado, la compilación y el proceso de ensamblado.Enlazado: una vez tengas todos los archivos
.olo último que debes hacer es enlazar todos los archivos para generar un archivo ejecutable. Este archivo contiene el código de máquina de todos los.opero organizado en un formato específico. En el caso de Linux el formato típico es.ELF. Ejecuta el siguiente comando para enlazar:ld min.o main.o. Verás el siguiente resultado:
ld: warning: cannot find entry symbol _start; defaulting to 0000000000401000
ld: main.o: in function main:
main.c:(.text+0x31): undefined reference to printf
Este resultado indica que no fue posible generar el ejecutable
(main.c:(.text+0x31): undefined reference to printf). Pero ¿Por qué?
la razón es que nos falta el archivo con el código de máquina de la función printf.
Esta función está prototipada en el archivo de cabecera (stdio.h), pero el archivo
no contiene el código fuente de printf. ¿Y dónde está el código entonces? este
código hace parte de la biblioteca glibc
que debes tener en tu sistema operativo y que contiene el código de máquina de varias
funciones, entre ellas, printf.
Una forma fácil de generar el ejecutable es utilizar de nuevo gcc. Este comando
se encargará de suministrarle a ld todo los archivos con código máquina necesarios para
generar nuestro ejecutable: gcc min.o main.o -o main.
Evaluación
Advertencia
FECHA DE ENTREGA Y SUSTETACIÓN
La evaluación debe estar en el repositorio que les daré en Github y sustentada para el viernes 23 de septiembre de 2022.
Enunciado
Una estructura de datos típica en la solución de problemas de entretenimiento digital es la LISTA. Te voy a proponer un proyecto que hace uso de LISTAS; sin embargo, el proyecto está INCOMPLETO. Tu misión será completar el proyecto y pasar todos los vectores de prueba. Una vez termines la solución debes estudiarla detenidamente para preparar la sustentación. Dicha sustentación será una pregunta relacionada con el proyecto.
El primer reto es entender el PROBLEMA. Te dejo algunas pistas:
El programa es interactivo, es decir, para funcionar requiere que el usuario le ingrese órdenes.
Puedes comenzar a explorar en el archivo main.c.
Nota que para cada comando el programa realizará acciones. Las acciones llamarán funciones. Son precisamente las funciones que se llaman las que tendrás que completar.
Advertencia
ESTA EVALUACIÓN ES EN EQUIPO
Arma tu equipo de trabajo. Uno de los miembros debe crear el equipo y los demás se unen a ese equipo. El enlace para crear el equipo y tener acceso al repositorio está aquí.
En el directorio tests encontrarás todos los vectores de prueba. Serán 19 en total. Los archivos .desc contienen la descripción de la prueba. Los archivos .in te muestran la secuencia de comandos que ejecutará el programa. Los archivos .out te muestran la salida esperada. Los archivos .err almacenarán el mensaje de error esperado. Los archivos .rc el valor que se espera que retorne el programa al terminar. Los archivos .run te muestran cómo se ejecuta la prueba.
Advertencia
¿QUÉ HAGO PARA PASAR UNA PRUEBA?
Observa cuáles son los comandos que ejecutará el programa (archivo .in) y qué se espera que haga (archivo .out). Debes implementar la lógica del programa que permita conseguir el resultado.
MUY IMPORTANTE
No uses ninguna función para imprimir en pantalla a menos que sean las que ya están en el código o donde se te pide que las uses. En la función
ListEventsdeberás imprimir cada evento usando la cadena formateada"%s\n"y en caso de tener una lista vacía usarás esta funciónprintf("empty\n");. La razón de esto es que tu programa será verificado automáticamente y por tanto, si imprimes información no esperada es posible que las pruebas automáticas fallen.Para compilar, cámbiate el directorio donde están los archivos
.cy ejecuta el comandomake. Ten en cuanta que con el comandomake cleanpuedes limpiar todos los archivos compilados y luego conmakevolver a generarlos.Para hacer las pruebas puedes correr todos los vectores de prueba así:
./test-main.sh
O si quiere correr solo un vector, por ejemplo, el 10, lo haces así:
./test-main.sh -t 10Verifica que estás usando correctamente la memoria dinámica. Para ello instala valgrind y luego realiza la verificación. Te dejo los comandos, primero para instalar valgrind y luego para verificar.
sudo apt update sudo apt install valgrind
valgrind ./main < ./tests/12.in
Si la memoria está bien verás algo así en el resumen:
==17813== ==17813== HEAP SUMMARY: ==17813== in use at exit: 0 bytes in 0 blocks ==17813== total heap usage: 12 allocs, 12 frees, 5,360 bytes allocated ==17813== ==17813== All heap blocks were freed -- no leaks are possible ==17813== ==17813== For lists of detected and suppressed errors, rerun with: -s ==17813== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
La salida anterior se consigue ejecutando el programa con el vector de prueba 12.in. Con este vector de prueba, el programa realiza 12 reservas con malloc y detecta 12 liberaciones con free. Por tanto, al final indica que no hay errores.
Criterios de evaluación
Cada vector de prueba tiene un puntaje. El puntaje de cada punto lo puedes encontrar en el
archivo .github/classroom/autograding.json que está en tu repositorio.