Desarrollo de Aplicaciones en C y Makefiles
Desarrollo de Aplicaciones en C y Makefiles
31/08/2018
Desarrollo de aplicaciones
¿Qué necesitamos como mı́nimo para desarrollar una aplicación de escritorio en lenguaje C?
Desarrollo de aplicaciones
¿Qué necesitamos como mı́nimo para desarrollar una aplicación de escritorio en lenguaje C?
Rta.: Un compilador
Desarrollo de aplicaciones
¿Qué necesitamos como mı́nimo para desarrollar una aplicación de escritorio en lenguaje C?
Rta.: Un compilador
Surgen problemas:
Gestión del proyecto
Desarrollo de aplicaciones
¿Qué necesitamos como mı́nimo para desarrollar una aplicación de escritorio en lenguaje C?
Rta.: Un compilador
Surgen problemas:
Gestión del proyecto
Edición de los archivos de código fuente
Desarrollo de aplicaciones
¿Qué necesitamos como mı́nimo para desarrollar una aplicación de escritorio en lenguaje C?
Rta.: Un compilador
Surgen problemas:
Gestión del proyecto
Edición de los archivos de código fuente
Depuración
Desarrollo de aplicaciones
¿Qué necesitamos como mı́nimo para desarrollar una aplicación de escritorio en lenguaje C?
Rta.: Un compilador
Surgen problemas:
Gestión del proyecto
Edición de los archivos de código fuente
Depuración
Desarrollo de aplicaciones
¿Qué necesitamos como mı́nimo para desarrollar una aplicación de escritorio en lenguaje C?
Rta.: Un compilador
Surgen problemas:
Gestión del proyecto
Edición de los archivos de código fuente
Depuración
Serı́a deseable contar con un entorno de desarrollo...
El paquete binutils
Incluye herramientas de desarrollo a diferentes niveles. Entre ellas:
ld: El enlazador de GNU.
as: Compilador de lenguaje ensamblador.
addr2line: Convierte direcciones a nombres de archivos y números de lı́nea
ar: Utilidad para la creación de librerı́as.
nm: Muestra los sı́mbolos presentes en un archivo objeto.
objcopy: Copia y convierte archivos objeto.
objdump: Muestra información contenida en un archivo objeto.
readelf: Muestra información contenida en un archivo binario con formato ‘ELF’.
size: Informa los tamaños de las secciones de un archivo objeto o binario.
strip: Elimina sı́mbolos contenidos en un archivo binario.
[Link]
y todo lo demás
gcc: ‘The GNU C Compiler’
Gcc ARM
[Link]
GCC for windows (tdm-gcc)
[Link]
Ambos paquetes incluyen versiones apropiadas de gdb
Eclipse:
Primero, Java
[Link] [Link]
Paquete oficial
[Link]
Eclipse + CDT + Pluggins para ARM
[Link]
-L: Indica la ruta de búsqueda a las librerı́as que pueden requerirse durante el proceso de
enlazado. Si se creara una librerı́a con un fin particular, la ubicación de esta deberı́a indicarse
con este parámetro. Es similar al parámetro -I durante el proceso de compilación.
-l: Indica que librerı́a debe utilizarse durante el proceso de enlazado. En caso de utilizar
enlazado estático, el nombre de la librerı́a deberá ser precedido por ‘lib’ y tener extensión ‘a’.
Ası́ la librerı́a matemática se denomina libm.a y se incluye a través de -lm
Makefiles
Claro está que el desarrollo de aplicaciones requiere trabajar con multitud de archivos y repetir el
proceso de construcción de las aplicaciones.
Para resolver esto existen diferentes herramientas para la gestión de proyectos de software. Una de
las más utilizadas es GNU make.
Esta aplicación procesa archivos de texto conteniendo directivas para lograr diferentes efectos. Si
bien ha sido desarrollada para la gestión de proyectos de software, puede utilizarse para automatizar
diferentes tareas.
Makefiles
La estructura general de un archivo makefile contiene objetivos, requerimientos y las acciones
necesarias para lograr los objetivos a partir de los requerimientos. Por ejemplo:
app : main . c
gcc - O0 -g -c main . c -o main . o
gcc main . o -o app
En este caso el objetivo es un archivo llamado app, el requerimiento es un archivo llamado main.c
y, bajo ambos se encuentran la lista de acciones a ejecutar para lograr el objetivo.
Si las lı́neas anteriores se guardan en un archivo llamado makefile, y junto a él se encuentra el
archivo main.c, tras ejecutar el comando make construiremos la aplicación.
Makefiles
Makefiles
Makefiles
Makefiles
Algunos detalles
Cuando se invoca al comando make, por defecto, busca un archivo con el nombre makefile en
el directorio donde se produce la invocación. Si existe, es interpretado. Si se quisiera trabajar
con un archivo con otro nombre, es posible, pero el nombre del archivo a procesar debe indicarse
con el parámetro -f. Por ejemplo:
$make -f [Link]
Make utiliza la fecha y hora de creación de los requerimientos y los objetivos para detectar
cambios. Por lo que si ambos están sincronizados entiende que no hay cambios y no repite el
proceso de construcción.
Si en un archivo makefile existen múltiples objetivos, el objetivo que se tratará de alcanzar
será el primero.
Es por esto que cuando se tienen diferentes archivos constitutivos de una aplicación se tiene
un objetivo que es la aplicación en sı́, que tiene como requerimientos otros objetivos. De este
modo se encadena el proceso de construcción de las aplicaciones.
Si un makefile tiene múltiples objetivos y se desea invocar un objetivo particular es posible
hacerlo indicando el nombre del objetivo a alcanzar. Por ejemplo:
$make -f unMakefile unObejtivo
Algunos detalles
Por ejemplo, si se separa el proceso de compilación del de enlazado, se podrı́a trabajar con múltiples
objetivos.
app : main . o
gcc main . o - lm -o app
main . o : main . c
gcc -c - O0 -g main . c -o main . o
Si se invocase solo make se construirá la aplicación app, pero si se utilizara como make main.o solo
se producirı́a el proceso de compilación generando el archivo main.o.
Algunos detalles
Es común que un makefile tenga objetivos especiales que no resultan en archivos fı́sicos. Por
ejemplo, es común utilizar un objetivo llamado clean para eliminar los archivos resultantes del
proceso de compilación. En este caso, estos objetivos se indican utilizando la palabra clave PHONY.
. PHONY : all clean
all : app
app : main . o
gcc main . o - lm -o app
main . o : main . c
gcc -c - O0 -g main . c -o main . o
clean :
rm - rf main
rm - rf main . o
De modo similar al caso del objetivo clean, también suele utilizarse un primer objetivo llamado
all que realiza el proceso completo de compilación de una aplicación.
Algunos detalles
Make soporta el uso de variables. Las variables no tienen tipo y puede contener una palabra, una
lista de palabras o el resultado de la ejecución de una aplicación. Si al momento de invocar al
comando make se indica el valor de una variable, el valor con el que se haya inicializado en el script
será reemplazado por el valor dado. Por ejemplo, si se tiene un makefile como:
to := " Mundo "
. PHONY : all
all :
@echo " Hola $ ( to )! "
$make to=Clase
Hola Clase!
Algunos detalles
Al utilizar el carácter previo a una acción, hace que no se muestren por pantalla las acciones
que se ejecutarán.
El uso de variables permite lograr makefiles más flexibles, Por ejemplo, si en vez de invocar
al compilador por su nombre, lo hiciéramos a través de una variable, podrı́amos cambiar el
compilador utilizado a voluntad.
ccName = gcc
all : app
app : main . o
@$ ( ccName ) main . o - lm -o app
@echo " Finalizado ! "
main . o : main . c
@echo -n " Compilando main . c ... "
@$ ( ccName ) -c - O0 -g main . c -o main . o
@echo " listo ! "
clean :
@rm - rf main
@rm - rf main . o
all : app
app : main . o
@$ ( ccName ) main . o - lm -o app
@echo " Finalizado ! "
%. o : %. c
@echo -n " Compilando $ <... "
@$ ( ccName ) -c - O0 -g $ < -o $@
@echo " listo ! "
clean :
@rm - rf main
@rm - rf main . o
Funciones
Existe un amplio conjunto de funciones definidas que pueden ser utilizadas para el desarrollo de
makefiles.
En general, las funciones se invocan como:
Una función particularmente útil es wildcard. Esta función retorna la lista de archivos que cumplen
con una condición particular en un determinado path o, si se da un nombre como argumento,
retornará el nombre del archivo en caso de existir o una cadena vacı́a. Por ejemplo:
srcLst:=$(wildcard src/*.c) Si dentro de la carpeta src existieran los archivos main.c,
util.c y main.h, la variable $(srcLst) contendrá los
valores src/main.c src/util.c
Condicionales
El comienzo de los makefile suelen contener declaración de variables, invocaciones a funciones,
invocaciones a comandos y, según el resultado obtenido, puede ser necesario modificar o establecer
el valor de variables.
Para realizar estas tareas existe la posibilidad de utilizar estructuras condicionales:
ifeq (arg1, arg2) ifeq (arg1, arg2) ifeq (arg1, arg2)
[acciones] [acciones] [acciones]
endif else else ifeq (arg1, arg3)
[acciones] [acciones]
endif else
[acciones]
endif
Condicionales
Algunos ejemplos:
all : all :
@echo $ ( msg ) @echo $ ( msg )
Comandos de utilidad
Desde un makefile es posible invocar cualquier comando o programa a través de la función shell.
En algunos casos el uso de algunos parámetros resulta de utilidad.
mkdir -p path/deseado
Crea toda la estructura de directorios indicado. Si la estructura de directorios dada existe, no
generará un error.
rm -rf path
Borrará de forma recursiva el path indicado. Si el path indicado no existe no producirá un
error.
which comando
Retornará el path completo al comando indicado en el sistema de archivos. Si no se encuentra
el comando o programa en las rutas de búsquedas predeterminadas retornará una cadena
vacı́a.
cat archivo
Envı́a el contenido del archivo indicado a la salida estándar. Al ser invocado desde la función
shell en un makefile el contenido del archivo puede ser asignado a una variable.
echo $(msg)
Envı́a a la salida estándar el texto contenido en la variable $(msg) agregando un fin de lı́nea
a continuación del mismo. Si no se desea este agregado puede utilizarse el modificador -n.
all :
@echo $ ( gcc )
Ejemplos como este son muy comunes al momento de trabajar con sistemas embebidos. Un archivo
establece la configuración del proyecto a partir de inicializar las variables de modo adecuado mientras
que el otro efectivamente realiza el proceso de construcción.
Aspectos de interés:
Acceso a la variable saludo desde el makefile secundario definida en el makefile principal.
Paso del parámetro msg al makefile secundario.
Construcción de librerı́as
El primer paso en la construcción de aplicaciones es la compilación. Finalizada esta se da el proceso
de enlazado para obtener la aplicación final.
Si se tienen módulos de software que son utilizados por la aplicación suelen compilarse como
librerı́as. Para esto se realiza el proceso de compilación y luego, se empacan los archivos objetos
resultantes en una librerı́a de enlazado estático. Esta librerı́a puede ser utilizada posteriormente
durante el proceso de enlazado de la aplicación sin necesidad de volver a compilar los archivos de
código fuente.
Para lograr esto es necesario utilizar el comando ar, los argumentos tı́picos son:
c: Crea un nuevo contenedor (librerı́a).
s: Agrega un ı́ndice al contendor.
r: Inserta los archivos objetos indicados en la librerı́a. Si se cuenta con múltiples definiciones
de la misma entidad, serán sobre escritas.
Ejemplo:
ar rcs libutils.a calc.o datos.o io.o
Se creará una librerı́a de enlazado estático llamada libutils.a a partir del código objeto contenido
en los archivos calc.o, datos.o e io.o. Para usar esta librerı́a en el proceso de enlazado de una
aplicación se podrı́a utilizar una lı́nea como:
gcc main.c -lutils -L. -Iinc/ -o main