Semana 8: Unidad 3¶
En este punto del curso ya conocemos cómo funciona un computador, también conocemos el concepto de programa almacenado y cómo hacer para convertir un programa de lenguaje ensamblador a lenguaje de máquina.
A partir de ahora vamos a emprender un viaje por varios de los componentes de software relacionados con los sistema de cómputo. En esta unidad vamos a analizar las herramientas necesarias para transformar un programa en lenguajes como C, C++ y C# a lenguaje ensamblador.
Propósito de aprendizaje¶
Comprender cómo se transforma un programa escrito en lenguajes como C, C++ y C# a lenguaje ensamblador.
Código de honor¶
Al realizar los ejercicios esta unidad se espera que hagas lo siguiente:
- Colabora con tus compañeros cuando así se indique.
- Trabaja de manera individual cuando la actividad así te lo proponga.
- NO DEBES utilizar sitios en Internet con soluciones o ideas para atacar el proyecto.
- NO DEBES hacer uso de foros para buscar soluciones al proyecto.
- ¿Entonces qué hacer si no me funciona algo? Te propongo que experimentes, crea hipótesis, experimenta de nuevo, observa y concluye.
- NO OLVIDES, este curso se trata de pensar y experimentar NO de BUSCAR soluciones en Internet.
Actividades¶
Actividad 1¶
- Fecha: agosto 25 de 2020 - 8 a.m.
- Descripción: introducción a la Unidad 3
- Recursos: ingresa al grupo de Teams
- Duración de la actividad: 1 hora 40 minutos de discusión
- Forma de trabajo: colaborativo con solución de dudas en tiempo real.
Vamos a comenzar nuestro viaje explorando una herramienta conocida como la línea de comandos, disponible en casi todos los sistemas operativos. Para ello te propongo realizar la siguiente guía.
Actividad 2¶
- Fecha: agosto 25 a agosto 27 de 2020
- Descripción: introducción al lenguaje C
- Recursos: actividad de trabajo autónomo
- Duración de la actividad: 4 horas
- Forma de trabajo: individual, trabajo autónomo.
Para realizar la siguiente guía vas a necesitar un entorno de prueba. En la sección Ejercicios te he dejado información para que tengas ese entorno en tu computador local; sin embargo, para que puedas comenzar de una vez te voy a recomendar dos sitios donde puedes compilar y ejecutar programas online. Usa el que más te guste.
En este enlace encontrarás una guía básica del lenguaje C.
Note
¡Alerta de Spoiler!
En este enlace , se encuentra la solución a algunos puntos de la guía introductoria a C (ojo, no todos). Te recomiendo hacer los ejercicios sin recurrir a la solución.
Ejercicios¶
Ejercicio 1: entorno de trabajo¶
Para poder trabajar en los ejercicios que te propondré vas a necesitar un ambiente de trabajo. Te propongo que instales en una USB o en una partición de tu computador el sistema operativo Linux. Te preguntarás si puedes instalarlo en una máquina virtual. Lo puedes hacer pero usualmente no lo recomiendo porque la experiencia de uso no resulta agradable si tu sistema es muy lento.
Vas a necesitar dos memorias USB. Una grande (> 16GB), donde instalarás tu sistema operativo y otra más pequeña (8GB) donde grabaras el instalador. Trata de utilizar la USB más rápida y más grande para instalar tu sistema operativo.
Te voy a dejar unos videos de ayuda:
- Este video te muestra como grabar en la USB pequeña el instalador. En este caso la distribución es PopOS, es la misma que yo uso; sin embargo, puedes grabar la que más te guste, por ejemplo Ubuntu. Ten presente que la versión del video no será la última. También, debes investigar cómo entrar al menú de configuración de tu BIOS para que ajustes el orden de boot. Nota que debes darle prioridad a la USB para que al tenerle conectada arranques el instalador del sistema operativo.
- Ahora, este video video te mostrará cómo instalar, usando la USB pequeña con el instalador, tu sistema operativo en la USB grande. Te recomiendo iniciar a ver el video en el minuto 6:29, donde comienza en si el proceso de instalación. Una vez termines de instalar Linux en la USB grande, NO OLVIDES desconectar la USB pequeña para que tu computador inicie con la versión instalada de Linux en la USB grande.
Ejercicio 2: instala las herramientas¶
Abren la terminal y ejecuta los comandos:
1 2 3 4 | $ sudo apt update
$ sudo apt upgrade
$ sudo apt install build-essential
$ sudo apt install gdb
|
Ejercicio 3: instala un par de entornos de desarrollo¶
Para el curso te recomiendo que instales dos entornos de desarrollo:
- Eclipse
- Visual Studio Code
Eclipse te permitirá tener un depurador visual de código, pero la verdad es un poco lento. Visual Studio, no tiene un depurador visual tan rico, pero es muy liviano. Yo uso ambos. Normalmente trabajo con Visual Studio Code y cuando algo no me funciona lo pruebo con Eclipse.
Actividad 3¶
- Fecha: agosto 27 de 2020 - 10 a.m.
- Descripción: introducción al lenguaje C
- Recursos: actividad de trabajo autónomo
- Duración de la actividad: 4 horas
- Forma de trabajo: individual, trabajo autónomo.
Actividad 4¶
- Fecha: agosto 25 a septiembre 1 de 2020
- Descripción: introducción al lenguaje C
- Recursos: actividad de trabajo autónomo
- Duración de la actividad: 4 horas
- Forma de trabajo: individual, trabajo autónomo.
Para las actividades 3 y 4 te propongo que continues estudiando un poco más el lenguaje de programación C.
Primero te voy a proponer que hagas dos guía para que trabajes los conceptos básicos y luego una serie de ejercicios que te permitirán practicar varias de las cosas que has hecho hasta ahora.
Realiza esta guía sobre punteros, arreglos y memoria dinámica.
Note
¡Alerta de Spoiler!
En este enlace se encuentra la solución a la guía de punteros, arreglos y memoria dinámica.
Realiza esta guía sobre estructuras de datos y archivos.
Ejercicios¶
Ejercicio 1: entrada/salida¶
En la guía introductoria del lenguaje C se discutió la
función scanf para realizar operaciones de entrada en
C. Al realizar el ejercicios final, la calculadora,
¿Notaste algún comportamiento extraño del
programa al leer caracteres? Específicamente scanf("%c",&var).
Ten presente que al introducir texto en la terminal, además de los caracteres visibles, se introduce un ENTER. Así, por ejemplo, al introducir el número 325 y luego presionar ENTER, se están ingresando 4 bytes: 0x33 0x32 0x35 0x0A. los tres primeros bytes corresponden a los códigos ASCII de cada dígito del número 325 y el 0x0A corresponde al código ASCII del ENTER o nueva línea (NEW LINE).
Considere el siguiente código:
1 2 3 4 5 6 7 8 9 10 11 12 13 | #include <stdio.h>
int main()
{
int num;
char key;
printf("Prueba a scanf. Ingrese el numero 325 y presione ENTER:\n");
scanf("%d",&num);
printf("Ingrese cualquier tecla para terminar y presione ENTER:\n");
scanf("%c",&key);
return 0;
}
|
Ejecuta el código anterior. ¿Cuál es el resultado? ¿Por qué?
El primer scanf (scanf("%d",&num);) buscará en el flujo de entrada una
secuencia de bytes que comience con un carácter numérico y parará de leer
una vez detecte un carácter no numérico, el cual, dejará intacto en el flujo
de entrada. En este caso, scanf("%d",&num); sacará del flujo
los bytes 0x33 0x32 0x35, correspondientes a '3' '2' '5',
y dejará en el flujo el byte 0x0A (correspondiente al ENTER). Luego
convertirá la cadena de 3 bytes en ASCII al número que representan, es decir,
al 325 que en base 16 sería 0x0145 (comprueba esto con la calculadora del
sistema operativo)
El segundo scanf scanf("%c",&key); leerá un carácter del flujo de entrada.
En este caso dicho carácter está disponible y corresponde al ENTER dejado
por el scanf anterior.
¿Cómo solucionar este problema? Una posible solución será (aunque hay otras más):
1 2 3 4 5 6 7 8 9 10 11 12 13 | #include <stdio.h>
int main()
{
int num;
char key;
printf("Prueba a scanf. Ingrese el numero 325 y presione ENTER:\n");
scanf("%d",&num);
scanf("%c",&key); // Saco del flujo el ENTER
printf("Ingrese cualquier tecla para terminar y presione ENTER:\n");
scanf("%c",&key);
return 0;
}
|
Ejercicio 2: entrada/salida¶
Para complementar el ejercicio anterior, se propone analizar otros ejemplos (Tomados de este enlace).
1 2 3 4 5 6 7 8 9 | #include <stdio.h>
int main(void)
{
int a = 10;
printf("enter a number: ");
scanf("%d", &a);
printf("You entered %d.\n", a);
}
|
Ingresa un número y ENTER. ¿Qué ocurre? Ahora ingresa una palabra y ENTER. ¿Qué ocurre? ¿Por qué?
Ejercicio 3: scanf return¶
scanf devuelve la cantidad de conversiones realizadas. Analiza este ejemplo (ingresa CRTL+C si algo sale mal):
1 2 3 4 5 6 7 8 9 10 11 12 13 | #include <stdio.h>
int main(void)
{
int a;
printf("enter a number: ");
while (scanf("%d", &a) != 1)
{
// input was not a number, ask again:
printf("enter a number: ");
}
printf("You entered %d.\n", a);
}
|
¿Por qué funciona así el programa? Recuerda el ejercicio 1.
Ejercicio 4: cadenas¶
Compila el código que se muestra a continuación así:
gcc -Wall -fno-stack-protector tmp.c -o tmp
Ejecuta el programa con estos vectores de prueba cuando se pregunte por el nombre:
- juan
- juan-fernan
- juan-fernando-franco
1 2 3 4 5 6 7 8 9 | #include <stdio.h>
int main(void)
{
char name[12];
printf("What's your name? ");
scanf("%s", name);
printf("Hello %s!\n", name);
}
|
Explique cómo funciona el programa en cada caso.
Ejercicio 5¶
Repite el ejercicio anterior pero esta vez compilando
sin -fno-stack-protector.
Ejercicio 6¶
Finalmente repita el ejercicio anterior, pero esta vez
usando el siguiente código y compilando sin -fno-stack-protector
1 2 3 4 5 6 7 8 9 | #include <stdio.h>
int main(void)
{
char name[40];
printf("What's your name? ");
scanf("%39s", name);
printf("Hello %s!\n", name);
}
|
Explica por qué en scanf especificamos un 39 sabiendo que name puede almacenar 40 caracteres. Recuerda, de la primera guía, que todas las cadenas en C deben terminar con un 0.
Ejercicio 7¶
Usando el código anterior ingresa: juan fernado franco. ¿Cuál es el resultado?
Ejercicio 8¶
Escribe el siguiente código:
1 2 3 4 5 6 7 8 9 | #include <stdio.h>
int main(void)
{
char name[40];
printf("What's your name? ");
scanf("%39[^\n]", name);
printf("Hello %s!\n", name);
}
|
Nota la línea:scanf("%39[^\n]", name);. En este caso le estamos diciendo a
scanf que lea hasta 39 caracteres y hasta que encuentre un ENTER (\n). También
es posible indicarle a scanf que lea mientras que los caracteres estén en una
lista, por ejemplo: scanf("%39[a-z]", name);.
Ejercicio 9¶
¿Entonces qué usamos para leer la entrada?
Ahora que conocemos mejor los punteros y los arreglos podemos explorar la
función fgets: char *fgets(char *str, int n, FILE *stream). A esta
función le debemos pasar la dirección del buffer donde queremos colocar
los caracteres, la cantidad de caracteres y el flujo. fgets termina de leer
el flujo cuando encuentre un ENTER. Dicho ENTER se saca del flujo
Analiza el funcionamiento de fgets:
1 2 3 4 5 6 7 8 9 10 11 | #include <stdio.h>
int main(void)
{
char name[40];
printf("What's your name? ");
if (fgets(name, 40, stdin))
{
printf("Hello %s!\n", name);
}
}
|
NOTA que en name quedará también el ENTER. Entonces para eliminarlo simplemente hacemos:
1 2 3 4 5 6 7 8 9 10 11 12 13 | #include <stdio.h>
#include <string.h>
int main(void)
{
char name[40];
printf("What's your name? ");
if (fgets(name, 40, stdin))
{
name[strcspn(name, "\n")] = 0;
printf("Hello %s!\n", name);
}
}
|
strcspn buscará en la cadena name el primer match con
\n y devolverá la posición en name en la cual fue encontrado
el match.
Ejercicio 10¶
(Este ejercicio es tomado de aquí)
Relación arreglos y punteros
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | #include<stdio.h>
int main()
{
int *p;
int (*ptr)[5];
int arr[5];
p = arr;
ptr = &arr;
printf("p = %p, ptr = %p\n", p, ptr);
p++;
ptr++;
printf("p = %p, ptr = %p\n", p, ptr);
return 0;
}
|
Ejecuta el programa anterior. El resultados es:
1 2 | p = 0x7fff4f32fd50, ptr = 0x7fff4f32fd50
p = 0x7fff4f32fd54, ptr = 0x7fff4f32fd64
|
En la expresión int * p; p es una variable de tipo
int *. En este tipo de variables se almacenan las
direcciones de variables de tipo int. Por tanto, *p
(sin colocar int antes del *) es de tipo int porque
p es de tipo int *.
En la expresión int (*ptr)[5]; ptr es una variable de tipo
int (*)[5]. En este tipo de variables se almacenan direcciones
de variables de tipo int [5], es decir, variables de tipo
arreglo de cinco posiciones. Por tanto, *ptr es de tipo
int [5] porque ptr es de tipo int (*)[5].
En la expresión p = arr; arr es el nombre del arreglo y un puntero
al primer elemento del arreglo.
En este caso arr es de tipo int * porque el primer elemento
del arreglo es de tipo int. Por tanto, *arr
será tipo int.
En la expresión ptr = &arr; &arr es la dirección del arreglo.
&arr es tipo int (*)[5].
La expresión printf("p = %p, ptr = %p\n", p, ptr); imprime el
contenido de p y ptr. Según el resultado
(p = 0x7fff4f32fd50, ptr = 0x7fff4f32fd50`), la dirección del
arreglo y del primer elemento del arreglo es la misma; sin embargo,
como p es tipo int *, la expresión p++ hará que p apunte
(almacene la dirección) al siguiente entero. En cambio, en la
expresión ptr++; ptr apuntará al siguiente arreglo de 5
enteros (5 enteros ocupan 20 bytes en memoria considerando
que cada entero ocupa 4 bytes), ya que ptr es de tipo
int (*)[5].
Ejercicio 11: análisis de una expresión más compleja¶
El siguiente ejercicio es más complejo que el anterior, sin embargo, se analiza de igual manera. Considera el siguiente código:
1 2 3 4 5 6 7 8 9 10 | #include <stdio.h>
int arr[3][4] = { {1,2,3,4}, {5,6,7,8}, {9,10,11,12} };
int main(void) {
int (*p)[3][4] = &arr;
printf("%d\n", ( (*p)[2] )[3] );
printf("%d\n", *( *(*p + 2) + 3 ) );
return 0;
}
|
arr es un arreglo de arreglos, es decir, es una arreglo de 3 arreglos
de 4 enteros cada uno.
arr es el nombre del arreglo de arreglos y un puntero al primer elemento
del arreglo. Por tanto, arr es de tipo int (*)[4] ya que el primer elemento
de arr es un arreglo de tipo int [4].
p es un puntero que almacena la dirección de un arreglo de arreglos.
Por tanto, p es de tipo int (*)[3][4].
Si p es de tipo int (*)[3][4] entonces *p será de tipo int [3][4] o
int (*)[4] (un puntero al primer elemento del arreglo de arreglos).
El operador [] en la expresión (*p)[2] es equivalente a *( *p + 2).
Como el tipo de (*p + 2) es int (*)[4] el tipo de *( *p + 2)
será int [4]. la expresión (*p)[2] accede al tercer elemento de arr, es
decir, a {9,10,11,12} que es de tipo int [4].
Por último, como (*p)[2] es tipo int [4], entonces ( (*p)[2] )[3] ) es
tipo int y corresponderá al cuarto elemento del tercer arreglo de arr.
Nota que ( (*p)[2] )[3] ) es equivalente a *( (*p)[2] + 3) que a su
vez es equivalente a *( * ( *p + 2)+ 3)
El programa imprimirá el número 12.
La expresión printf("%d\n", *( * ( *p + 2)+ 3)); al ser equivalente a
printf("%d\n", ( (*p)[2] )[3] ); también mostrará un 12.