Programacion


iconTutorial sobre Desarrollo con herramientas GNU

Este tutorial pretende explicar cómo desarrollar software usando las herramientas de GNU en lenguajes tales como C/C++ y/o objetive C. Se asume que el lector tiene experiencia en dichos lenguajes de programación.

IDEs / Entornos de desarrollo

Hoy en día existen alguno IDEs comerciales o libres pero no es mi intención explicarlos. Las herramientas descriptas en este tutorial son de línea de comando. Para editar la fuentes de un programa se puede uar cualquier editor de texto, no obstante algunos editores tienen características interesantes para los programadores:

  • cat < archivo.c

    El más rápido y simple; para hacer un "save & quit" basta presionar CTRL+D

  • vim archivo.c o vi archivo.c

    vi tiene soporte para sintaxis "highlight" aunque puede ser difícil de usar para quien no está acostumbrado.

  • emacs archivo.c o xemacs archivo.c

    Emacs es enorme, lento y tiene millones de funciones que no nunca serán usadas. Es totalmente configurable a través de un tipo de LISP donde se puede hacer de todo, desde redefinir teclas hasta nuevos modos de sintaxis highlight. La funcionalidad básica es relativamente fácil de usar, pero el resto...

  • pico archivo.c

    Pico es de los editores más fáciles de utilizar, no tiene sintaxis highlight pero es bien simple.

  • jed archivo.c o xjed archivo.c

    Un poco parecido a emacs, tiene soporte para sintaxis higlight pero no es lento ni grande como emacs (es el que yo prefiero)

  • joe archivo.c

    Dicen que también es parecido a emacs pero nunca lo usé.

    Etc, etc, etc. Existen decenas de editores de texto pero lo que están en la lista son los más conocidos (por mí).

gcc - El compilador C

El gcc es el compilador GNU con soporte para los lenguajes C, C++, objective C, Fortran y posiblemente otros. GCC asumirá cuál es el lenguaje usado por el código fuente según la extensión del archivo. Las siguientes extensiones son válidas:

.c - C
.h - header para C, C++ o Objective C
.C, .cc  .cxx - C++
.m - Objective C

Para compilar un archivo en C basta usar el siguiente comando:

gcc programa.c -o programa

Donde programa.c es el archivo que contiene el código fuente y programa es el archivo ejecutable a ser generado.

Se puede especificar más de un archivo fuente al mismo tiempo. Ejemplo:

gcc archivo1.c archivo2.c archivo3.c -o programao

Algunas opciones útiles de línea de comando:

-o [nombre del ejecutable]
define el nombre del archivo ejecutable a ser generado. Si esta opción no es usada el nombre usado by default es a.out
-g
incluye los símbolos necesarios pra la depuración del programa. Es recomendable usar esta opción siempre.
-Wall
genera mensajes extras de "warnings" (advertencias), útil para encontrar errores comunes.
-c
Genera un archivo objecto en vez de uno ejecutable, un archivo objecto tendrá la extensión .o
       Exemplo:
       gcc -c archivo1.c -Wall -g
       gcc -c archivo2.c -Wall -g

Para generar un ejecutable a partir de un archivo1.o y archivo2.o basta lo siguiente:

gcc arquivo1.o arquivo2.o -o programao

-Ipath
especifica un PATH para el directorio que contiene los archivos de include (.h)
Ejemplo:
        gcc -I/home/yo/include archivo.c -o programa
-Dsímbolo
hace que el archivo sea compilado con un símbolo #define
   Ejemplo:
              gcc -DDEBUG archivo.c -o programa
-lbiblioteca
especifica una biblioteca que será linkeada al programa
-Lpath
especifica un PATH para un directorio que contiene blibliotecas/libraries
Ejemplo
       gcc archivo.c -o programa -L/home/yo/lib -lAlgo
       Básicamente hay dos tipos de bibliotecas: estáticas,
       donde el código contenido en ellas son incluídas en 
       el programa, y  dinámicas, donde el código será linkeado 
       dinámicamente en el tiempo de ejecución del programa. 
       Las bibliotecas estáticas tiene nombres del tipo:
       libAlgo.a, mientras que las dinámicas tienen nombres 
       del tipo: libAlgo.so

gdb depurador

Cuando un programa realiza una operación ilegal, como dividir por cero, ejecución de una instrucción ilegal o acceso a un área de memoria no permitida, este programa será terminado con una senial del tipo SIGSEGV y se generará un archivo core, es lo que sucede cuando uno ve:

Segmentation fault (core dumped)

Los archivos core contienen una imagen de la memoria del proceso generada en el momento en que dicho programa ejecutó la operación ilegal. Para ver lo que sucedió se puede utilizar GDB (GNU debugger) para analizar cuál fue esa operación ilegal y sus motivos. Es neceario que el programa haya sido compilado con la opción -g para que gdb pueda mostrar información.

gdb programa core

Con esta instrucción gdb cargará el programa con el archivo core el en directorio actual.

Dado el siguiente programa:

#include <stdlib.h>
#include <stdio.h>

char *foo(char *str)
{
    char *p = NULL;

    strcpy(p, str);
    return p;
}

int main(int argc, char **argv)
{
    printf("%s\n", foo("Hello, World"));
    return 0;
}

Compilado de la siguiente forma:
    gcc hello.c -g -Wall -o hello

Y ejecutado como:

% ./hello
Segmentation Fault (core dumped)

Ejecutamos gdb: (nota: donde se lee sparc-sun-etc, leáse i686-gnu-linux-etc)

% gdb hello core
GNU gdb 4.18
Copyright 1998 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "how copying" to see the conditions.
There is absolutely no warranty for GDB. Type "how warranty" for details.
This GDB was configured as "sparc-sun-solaris2.5.1"...
Core was generated by ./hello'.
Program terminated with signal 11, Segmentation Fault.
Reading symbols from /usr/lib/libc.so.1...done.
Reading symbols from /usr/lib/libdl.so.1...done.
#0 0xef723d34 in strcpy () from /usr/lib/libc.so.1
(gdb)

Para hacer un backtrace (ver el contenido de la pila de ejecución) se usa el comando bt:

(gdb) bt
#0 0xef723d34 in strcpy () from /usr/lib/libc.so.1
#1 0x106c4 in foo (str=0x11288 "Hello, World") at hello.c:9
#2 0x106f8 in main (argc=1, argv=0xeffffaac) at hello.c:17

La primera línea del backtrace dice:

#0 0xef723d34 in strcpy () from /usr/lib/libc.so.1
 ^       ^          ^                   ^
 |       |          |                   |
 |       |          |                   biblioteca donde está definida la función
 |       |          |                   libc.so é la biblioteca estándar de C.
 |       |          |
 |       |          nombre de la función que estaba siendo ejecutada en el momento
 |       |          del aborto.
 |       |
 |       dirección de la operación que estaba siendo ejecutada. Generalmente no es muy
 |       úútil, a no ser que usted sea un "hardcore hacker". En ese caso no veo por qué
 |       está leyendo esto...
 |
profundidad de la pila de ejecución
quiere decir que esta función que estaba siendo ejecutada en el
momento en que el programa fue abortado.

Y lo de la segunda:

#1 0x106c4 in foo (str=0x11288 "Hello, World") at hello.c:9
 ^     ^       ^      ^                              ^    ^
 |     |       |      |                              |    |
 |     |       |      |                              |    número de la línea
 |     |       |      |                              |    que estaba siendo
 |     |       |      |                              |    ejecutada.
 |     |       |      |                              |
 |     |       |      |                              nombre del archivo donde fue
 |     |       |      |                              definida la función
 |     |       |      |
 |     |       |      los parámetros (y sus valores) con que esta función
 |     |       |      fue llamada. Observando el resto del backtrace, se puede
 |     |       |      ver que quién llamó a esta función con tal valor, fue
 |     |       |      main()
 |     |       |
 |     |       la función foo, que llamó strcpy() con parámetros
 |     |       absurdos.
 |     |
 |     etc etc
 |
 etc etc

Continuando el análisis, vimos que la función en donde ocurrió la operación ilegal fue strcpy(), o sea, una función del sistema. Suponiendo que el sistema esté libre de bugs tan estúpidos que un programa tan simple como este pueda encontrar, deducimos que la culpa es de quien llamó a strcpy(), o sea, foo()

Para cambiar el contexto de gdb al contexto de la función foo(), se usa el comando frame:

(gdb) frame 1 #1 0x106c4 in foo (str=0x11288 "Hello, World") at
hello.c:9
9 strcpy(p, str);
(gdb)

La función en donde se manifiesta el problema es strcpy() que se supone libre de problemas en sí misma. Entonces es probable que el problema se encuentre en lo parámetros pasados a ella:

(gdb) print p $1 = 0x0
(gdb) print str $2 = 0x11288 "Hello, World"

Aparentemente, el valor de str no tiene ningún error pero el de "p" es una dirección nula, que es la causa del problema. ahora para ver las fuentes donde esa función fue definida se usa el comando list

(gdb) list
4
5  char *foo(char *str)
6  {
7      char *p = NULL;
8
9      strcpy(p, str);
10
11     return p;
12 }
13

El comando list lista algunas líneas del programa en la función que está causando problemas. Para obtener más información sobre ella, use 'help list'. Ahora ya se está en condiciones de reparar el programa.

Otros comandos útiles:

run
corre el programa que fue cargado
next
avanza una línea en el programa siendo ejecutado (en el modo paso a paso)
step
avanza un comando en el programa siendo ejecutado. Es lo mismo que next, pero si hay una llamada de función, éstaentrará en la función
break <función>
coloca un breakpoint en una función (o programa detendrá la ejecución al llegar a esa función. help breakpoint mostrará otras opciones de sintaxis.
cont
continúa la ejecución normal del programa (salida del modo paso a paso)
quit
salir del gdb

Make

Make facilita bastante el mantenimiento de un programa con un tamanio razonable (más de 2 o 3 archivos fuente). Después de definir algunas reglas de administración basta tipear make para tener el programa compilado en lugar de tipear toda una línea de comando gcc.

Archivo en donde se debe colocar las reglas de compilación para un programa es Makefile

Ejemplo

# compilador a ser usado
CC = gcc
# flags a ser utilizados por el compilador a la hora de compilar las fuentes.
CFLAGS = -g -Wall
# bibliotecas a ser linkados al programa
LIBS = -lm
# lista de archivos fuente del programa
SRCS = archivo1.c archivo2.c archivo3.c
# lista de archivos objeto a ser generados a partir dos archivos fuente
OBJS = archivo1.o archivo2.o archivo3.o
# nombre del programa ejecutable a ser generado
PROG = programa
#
# Regla para compilación de dos archivos fuente (.c --> .o)
.c.o: $(SRCS)
	$(CC) -c $<
#
# Regla para linkear dos archivos objeto para un ejecutable
$(PROG): $(OBJS)
	$(CC) -o $@ $(OBJS) $(LIBS)
#
# make clean, para apagar arquivos .o e backup de fontes.
clean:
rm -f *.o
rm -f *~

Nota: deben utilizarse TABs en lugar de espacios en blanco.

Makefile puede ser adaptado para otros programas, sólo basta cambiar la lista de archivos fuente, objetos, nombre del programa y alguna flag o biblioteca que se precise.

Algunos includes y bibliotecas útiles.
Header Biblioteca Descripción
stdlib.h Contiene la mayoría de las rutinas estándar de ANSI, como malloc(), free() etc
stdio.h Estándar Contiene las rutinas relacionadas com I/O, como printf(), scanf(), fgets(), fopen() etc.
string.h Estándar Rutinas de manipulción de strings, como strcmp(), memcpy(), memset() etc. 
math.h -lm Rutinas matemáticas, como sin(), sqr(), exp() etc. 
unistd.h Estándar Rutinas específicas de Unix, como fork(), execvp(), kill() etc


Trademarks are owned by their owners.

Although the information given in this document is believed to be correct, the author will accept no liability for the content of this document. Use the tips and examples given herein at your own risk.

Copyright © 1999 by Brocoli. This document may be distributed only subject to the terms and conditions set forth in the LDP license.