Primera prueba de programación en C.
Vamos a compilar un programa de prueba en C, como los que
tendréis que hacer en las sesiones restantes:
- Cread un directorio para la prueba y posicionaos en él:
mkdir PRUEBA
cd PRUEBA
.
- La mayoría de las prácticas no necesitarán
más que un
fichero fuente, pero aquí consideraremos un caso más
general. El programa va a calcular la raíz cuadrada de
2. El primer fichero se llama
prueba.c
y
su contenido es el siguiente:
#include <stdio.h> /* Cabecera de la función printf */
#include "raiz.h" /* Cabecera de la función raiz_cuadrada */
int main(void)
{printf("La raíz cuadrada de 2 es %f.\n",
raiz_cuadrada(2.0));
}
Procurad que ninguna línea del programa pase de los 79
caracteres, es decir, que se pase a la siguiente línea,
y así se verá bien en cualquier lugar. El buen
sangrado de un programa ayuda bastante a su comprensión.
Aunque el estilo concreto depende del programador, os
recomendamos dar cuatro espacios por cada entrada de
sangrado. Además, os recomendamos que no sangréis
con el
tabulador, sino con espacios. De este modo, sabréis que
vuestro programa se verá igual de bien en cualquier editor.
Mirad cómo un programa sencillo puede resultar
complicado de entender con un mal sangrado:
#include <stdio.h> /* Cabecera de la función printf */
#include "raiz.h" /* Cabecera de la función raiz_cuadrada */
int main(void) {printf(
"La raíz cuadrada de 2 es %f.\n", raiz_cuadrada
(2.0)); }
Este programa compilará igual de bien que la versión
anterior, de hecho, el compilador los ve iguales.
- El segundo fichero que compone el ejemplo se llamará
raiz.c
. Es el siguiente:
#include <math.h> /* Cabecera de la función sqrt */
float raiz_cuadrada(float x)
{return sqrt(x);
}
- El tercer fichero es un fichero con la cabecera de la
la función
raiz_cuadrada
. Se llama
raiz.h
.
float raiz_cuadrada(float);
Es un error bastante común incluir el código de las
funciones
en los ficheros de cabecera (.h
).
Ahí sólo van
las cabeceras, como el propio nombre indica.
- En estos momentos, tiene que haber tres ficheros en el directorio
PRUEBA
. Miradlo con ls -l
.
Ya tenemos los ficheros fuente que componen el programa. Ahora
es el momento de crear los ejecutables. Para ello, debéis crear
lo que se denomina un makefile
. Un
makefile
es un fichero que le dará al ordenador una serie de reglas
acerca de los pasos que ha de ejecutar para llegar a producir el
ejecutable. Para recordar lo que vimos en teoría acerca de los
makefiles, pulsa aquí.
Recordemos también los pasos que hay que dar para llegar al
ejecutable:

El orden en que se escriben las reglas en un fichero
makefile
no importa, salvo la
primera, que ha de ser la más general. Si observamos el dibujo,
la primera regla debe decir algo así como: "Para
obtener el fichero ejecutable prueba
,
necesito los ficheros objeto
prueba.o
y raiz.o
y lo
obtengo mediante la orden gcc prueba.o
raiz.o -o prueba -lm
". Esta regla, traducida al lenguaje
del fichero makefile
quedaría así:
prueba: prueba.o raiz.o
<TAB>gcc prueba.o raiz.o -o prueba -lm
Observad cómo se añade -lm
a la orden de enlazado
porque el código de la función sqrt
no se
encuentra en raiz.o
, sino en la biblioteca del sistema
libm.a
. El código de la mayor parte de funciones
del C estándar se encuentra también en otra biblioteca de
funciones, la libc.a
pero, como excepción, no
hay que indicarle al enlazador que use esa biblioteca, lo
hace automáticamente.
El fichero makefile
completo queda así:
prueba: prueba.o raiz.o
<TAB>gcc prueba.o raiz.o -o prueba -lm
prueba.o: prueba.c raiz.h
<TAB>gcc -c prueba.c
raiz.o: raiz.c
<TAB>gcc -c raiz.c
Tenemos, pues, cuatro ficheros en el directorio
PRUEBA
. Podemos decirle al ordenador que
compile el programa:
<T>/usuarios/gyermo/PRIVADO/SO/PRACTS/PRUEBA$ ls -l
total 8
-rw-r--r-- 1 gyermo profes 156 Feb 16 12:01 makefile
-rw-r--r-- 1 gyermo profes 316 Feb 16 11:17 prueba.c
-rw-r--r-- 1 gyermo profes 110 Feb 16 11:18 raiz.c
-rw-r--r-- 1 gyermo profes 29 Feb 16 11:18 raiz.h
<T>/usuarios/gyermo/PRIVADO/SO/PRACTS/PRUEBA$ make
gcc -c prueba.c
gcc -c raiz.c
gcc prueba.o raiz.o -o prueba -lm
Vemos que tenemos los ficheros objeto (.o
) y
el ejecutable, que ya podemos ejecutar:
<T>/usuarios/gyermo/PRIVADO/SO/PRACTS/PRUEBA$ ls -l
total 132
-rw-r--r-- 1 gyermo profes 135 Feb 16 12:29 makefile
-rwxr-xr-x 1 gyermo profes 61440 Feb 16 12:37 prueba
-rw-r--r-- 1 gyermo profes 314 Feb 16 12:32 prueba.c
-rw-r--r-- 1 gyermo profes 988 Feb 16 12:37 prueba.o
-rw-r--r-- 1 gyermo profes 77 Feb 16 12:37 raiz.c
-rw-r--r-- 1 gyermo profes 29 Feb 16 12:37 raiz.h
-rw-r--r-- 1 gyermo profes 764 Feb 16 12:37 raiz.o
<T>/usuarios/gyermo/PRIVADO/SO/PRACTS/PRUEBA$ prueba
La raíz cuadrada de 2 es 1.414214.
Lo bueno de los ficheros makefile
es que si
realizáis alguna modificación, el ordenador
sólo realizará
las compilaciones necesarias. Probad a cambiar el fichero
prueba.c
para que calcule la raíz cuadrada de
3 y haced a continuación un make
.
Puede que, en
alguna circunstancia, deseéis que make
recompile
todo de nuevo. En ese caso, teclead touch *.c *.h
.
Esta orden actualiza la fecha de modificación de todos los
ficheros con extensiones .c
o .h
para que make
crea que los acabamos justo de
editar.
NOTA: Si no os funciona touch
,
en el servidor, probad con tocar
.
-
Argumentos y códigos de retorno de los programas.
Los programas que se lanzan en la shell pueden llevar argumentos
y devolver valores al sistema operativo. En el caso de la orden:
<T>/usuarios/gyermo/PRIVADO/SO/PRACTS/PRUEBA$ ls -l
total 132
-rw-r--r-- 1 gyermo profes 135 Feb 16 12:29 makefile
-rwxr-xr-x 1 gyermo profes 61440 Feb 16 12:37 prueba
[...]
diremos que el fichero ejecutable /bin/ls
se ha
ejecutado con un argumento (-l
). Diremos que
-l
es el argumento número 1 de esa ejecución.
Abusando del lenguaje, el propio nombre del fichero ejecutable
(en este caso ls
) se dice que es el argumento
número 0.
Los programas también devuelven un valor al sistema operativo.
Para conocer qué valor ha devuelto un programa, hay que dar
justo a continuación de haber ejecutado el
programa la orden echo $?
a la shell:
<T>/usuarios/gyermo/PRIVADO/SO/PRACTS/PRUEBA$ ls -l
total 132
-rw-r--r-- 1 gyermo profes 135 Feb 16 12:29 makefile
-rwxr-xr-x 1 gyermo profes 61440 Feb 16 12:37 prueba
[...]
<T>/usuarios/gyermo/PRIVADO/SO/PRACTS/PRUEBA$ echo $?
0
<T>/usuarios/gyermo/PRIVADO/SO/PRACTS/PRUEBA$ ls -l NoWomanNoCry.mp3
NoWomanNoCry.mp3 not found
<T>/usuarios/gyermo/PRIVADO/SO/PRACTS/PRUEBA$ echo $?
2
El valor devuelto por un programa ha de ser un número comprendido
entre 0 y 255. Es costumbre en UNIX que un valor de 0 signifique
ejecución exitosa y los valores mayores que cero indiquen error.
El prototipo de main
.
main
es la primera función que se
ejecutará dentro
de un programa. Todo ejecutable debe contener una función
main
. El prototipo es variable, sin embargo. Si
definimos el prototipo de la función main
de la
manera siguiente, podemos saber qué argumentos nos ha pasado
el usuario en esta ejecución y devolver un valor al sistema
operativo:
int main(int argc, char *argv[]);
El valor que devolveremos al sistema operativo será justo ese
entero que devuelve la función main
. Los argumentos
los recibiremos en los parámetros con los que el sistema operativo
va a invocar a la función main
cuando comience la
ejecución del programa.
El primer parámetro es un entero e indica
cuántos argumentos ha
tecleado el usuario, incluyendo el propio nombre del programa.
El segundo parámetro es un array de cadenas de caracteres de
longitud igual al número de argumentos y que contiene los propios
argumentos en sí.
Ejemplo: la función main
del programa ls recibe
los siguientes parámetros cuando un usuario teclea "ls
-l
":
Argumentos de main
para ls -l |
argc | 2 |
argv[0] | "ls" |
argv[1] | "-l" |
Nota: al ser argc
y argv
parámetros
de una función (la función main
), son variables
mudas, es decir, no tienen por qué llamarse así.
Lo que importa
es que coincida su tipo. De todos modos, es costumbre en C
denominarlos así (argc
el primero y
argv
el segundo).
Las llamadas al sistema.
Manejar bien las llamadas al sistemas es un objetivo muy
muy importante en esta parte de la asignatura. Una llamada al
sistema tiene
el aspecto de una función de C. Sin embargo, se trata de una
auténtica invocación al núcleo del
sistema operativo para que
realice una tarea para nuestro programa. En cada sesión de las
restantes se
presentarán unas cuantas llamadas al sistema para vuestro estudio.
Para usar una llamada al sistema en vuestros programas sólo
debéis conocer el nombre de la llamada,
qué parámetros hay que
pasarle, qué devuelve y el fichero de cabecera o los ficheros
de cabecera que tenéis que incluir en vuestro programa.
Las llamadas al sistema de cada sesión las podréis encontrar
aquí.
Es conveniente que manejéis este fichero con soltura. Para
una descripción detallada de lo que hace la llamada al sistema,
debéis consultar la página de manual de la llamada.
Por ejemplo,
man -s 2 chmod
. El -s 2
viene de que
todas las llamadas
al sistema se encuentran en la sección 2 del manual.
Ejemplo: si queremos cambiar los permisos del fichero
pepe.txt
para que todo el mundo tenga acceso
de lectura y escritura a él, la llamada al sistema sería:
#include <sys/types.h>
#include <sys/stat.h>
[...]
chmod("pepe.txt",0666);
Todas las funciones de biblioteca de C (p. ej.
printf
), se basan internamente en las llamadas
al sistema y sus páginas de manual se encuentran en la
sección 3c.
Condiciones de error.
Una llamada al sistema puede fallar. Por ejemplo, la llamada al
sistema del apartado anterior puede fallar porque el fichero
pepe.txt
no exista, porque no tengamos los permisos
adecuados para cambiar los permisos del fichero, etc. Es
responsabilidad del programador detectar cuándo se ha producido
un error. Normalmente sabemos que se ha producido un
error porque la llamada al sistema devuelve un valor concreto,
habitualmente -1 (menos uno). En cualquier caso, se puede
consultar el resumen o
las páginas de manual.
Por lo tanto, mejor que el código del apartado anterior
deberíamos poner algo así:
#include <sys/types.h>
#include <sys/stat.h>
[...]
if (chmod("pepe.txt",0666)==-1)
{fprintf(stderr,"No se puede cambiar los permisos.\n");
return 1;}
Para saber qué tipo de error se ha producido, debemos consultar
una variable global del sistema denominada errno
.
En la página de manual de la llamada al sistema con la que estemos
trabajando se nos dice qué
valores toma errno
según el error que se produzca.
En particular, en la página de manual de chmod podemos leer:
[...]
ERRORES
Si chmod() o fchmod() fallan, el modo del fichero sigue igual. A errno
se le asigna uno de los siguientes valores.
[...]
[ENOENT] Un componente de path o el fichero nombrado
por path no existe.
[...]
Por lo tanto, si queremos considerar ese error en concreto, el
código quedaría:
#include <sys/stat.h>
#include <errno.h>
[...]
if (chmod("pepe.txt",0666)==-1)
{if (errno==ENOENT)
fprintf(stderr,"El fichero pepe.txt no existe.\n");
else
fprintf(stderr,"Imposible cambiar los permisos debido a un error.\n");
return 1;}
Las acciones que el programa debe tomar si se produce un error
pueden variar. Puede ser que el programa se pueda recuperar del
error o puede que tenga que acabar su ejecución. En todo caso,
es el programador el que debe situarse en las circunstancias
de cada error y decidir. En el caso del ejemplo, decidimos
acabar con el programa y devolver 1 al sistema operativo.
La función de biblioteca perror
.
Como considerar todos y cada uno de los posibles errores de una
llamada al sistema sería una labor muy tediosa y costosa, existe
una función de biblioteca de C que nos ahorra todas estas
comprobaciones. La función perror
comprueba
la variable errno
y escribe por el canal de error
estándar del programa
una etiqueta que le pasamos como parámetro,
dos puntos y un mensaje indicativo del error que se ha producido.
La etiqueta suele hacer referencia al nombre del programa,
a la función dentro del programa o cualquier otra referencia
que sirva al programador para detectar en su código fuente
dónde se produjo el error.
Aunque es una función muy útil,
hay veces que no aporta toda la información deseada.
El código del apartado anterior puede quedar así usando la
función perror
:
#include <sys/stat.h>
#include <stdio.h>
[...]
if (chmod("pepe.txt",0666)==-1)
{char etiqueta[80];
sprintf(etiqueta,"%s:chmod:pepe.txt",argv[0]);
perror(etiqueta);
return 1;}
Práctica.
Como primera práctica (que no hay que entregar), hay que realizar
un programa que admita el nombre de un fichero como argumento.
El programa debe cambiar el grupo al que pertenece el fichero
al grupo cuyo identificador de grupo es el 666. El programa
debe detectar e imprimir las codiciones de error y devolver
0 al sistema operativo si no se produjeron errores y 1 en caso
contrario. Notas: en caso de que el fichero sea un enlace
simbólico, debe cambiar el grupo del propio enlace, no de
aquello a lo que apunta. El propietario del fichero debe
permanecer inalterado.
Soluciones y comentarios.
Órdenes de la shell relacionadas.
chmod
- modifica los permisos de un fichero
chown
- cambia el propietario de un fichero
chgrp
- cambia el grupo de un fichero
ln
- hace un enlace duro o simbólico a un fichero
rm
- borra un fichero
ls
- lista el contenido de un directorio
Funciones de biblioteca relacionadas.
-
-
LPEs.
- Cuando ejecuto
make
, me ocurre algo parecido
a esto:
<T>/usuarios/gyermo/PRIVADO/SO/PRACTS/PRUEBA$ make
Make: Must be a separator on rules line 3. Stop.
Pista: Las apariencias engañan.
Solución.
- Yo edito mis ficheros. Yo los compilo mediante
make
. Y, cuando voy a ejecutar la primera
prueba de esta sesión, me aparece:
<ENCINA>/home/gyermo/PRUEBA$ prueba
ksh: prueba: no encontrado
Y yo es que alucino porque el fichero prueba
sí está:
<ENCINA>/home/gyermo/PRUEBA$ ls
prueba prueba.c prueba.o raiz.c raiz.h raiz.o Makefile
¿Qué me está pasando?
Pista: Sólo el que busca, halla.
Solución.
-
Al ejecutar make
sobre
la práctica de esta
sesión, ocurre lo siguiente:
<T>/usuarios/gyermo/PRIVADO/SO/PRACTS/SESION1$ make
cc -c sesion1.c
cc: "sesion1.c", line 5: error 1588: "UID_NO_CHANGE" undefined.
*** Error exit code 1
Stop.
Pista: Buscando una aguja en un pajar.
Solución.
Cuando compilo la práctica
me da el siguiente error:
<T>/usuarios/gyermo/PRIVADO/SO/PRACTS/SESION1$ cc sesion1.c -o sesion1
cc: "sesion1.c", line 3: error 1705: Function prototypes are an ANSI feature.
He mirado en la línea 3 de mi programa y sólo está el
prototipo de la función main
.
Solución.
- El programa que cambia el grupo de los ficheros funciona
bien. Sin embargo cuando meto:
cambia *
para que me cambie el grupo de todos los ficheros del
directorio actual, sólo me cambia el grupo de uno de
ellos.
Solución.
He hecho la práctica de la sesión,
la he repasado mil veces, pero resulta que cuando la
ejecuto me da el error:
<ENCINA>/home/gyermo$ cambia pepe
lchown:pepe: Not owner
Solución.
Al compilar la práctica desde
Linux o Solaris, me dice que no reconoce la macro
UID_NO_CHANGE
. ¿Le han hecho un
"trabajo" al compilador y he de acudir a la
Pitonisa Lola?
Solución.
Ejecuto la práctica en Linux
y aparece el siguiente error:
~$ cambia pepe.txt
./cambia:chmod:pepe.txt: Operation not permitted
Solución.
- Véase también los
LPEs del ejercicio de esta
misma sesión