Sesión 11: “Módulos”
¿Recuerdas el ejercicio del LCG? ¿El generador de números aleatorio (generador congruencial lineal)? Vamos a retomarlo y vamos a ver cómo se modulariza un programa en C paso a paso.
Programa sin módulos
- Un programa sin módulos no es fácilmente reusable por ejemplo, este programa,
lcg2.c
:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
|
#include <stdio.h>
#include <stddef.h>
/* Park-Miller */
#define A 16807LU
#define C 0LU
#define M 2147483647LU
/*
* Variable con el siguiente número aleatorio,
* se inicializa con la semilla
*/
long unsigned int x = 12345LU;
/* Función de generación: LCG */
unsigned long int generar_aleatorio() {
unsigned long int aleatorio = x;
x = (A * x + C) % M;
return aleatorio;
}
/*
* Convención entre los programadores de C:
* se pasa la longitud del array en otro parámetro
*/
unsigned long int media(unsigned long int a[], size_t n) {
size_t i;
unsigned long int sum = 0;
for (i = 0; i < n; i++) {
/* CUIDADO: cierto riesgo de overflow */
sum += a[i];
}
return sum / n;
}
/* Array donde almacenaremos N números del generador */
#define N 1000000
unsigned long int aleatorio[N];
int main() {
int i;
for (i = 0; i < N; i++)
aleatorio[i] = generar_aleatorio();
for (i = 0; i < N; i++)
printf("%lu\n", aleatorio[i]);
printf("\n%lu\n", media(aleatorio, N));
return 0;
}
|
- Si queremos reusar el generador en un programa de verdad, no podríamos hacerlo directamente. Tendríamos que copiar y pegar el código.
- Obviamente esto no es aceptable si lo que queremos hacer son programas grandes.
- Vamos a hacer tres módulos:
generar
: contiene el programa principal (el main
)
medialu
: módulo con una sola función que calcula la media de un array de long unsigned int
lcg
: módulo con toda la “parafernalia” de un generador LCG
- Como en cualquier lenguaje de programación, un módulo tiene una parte pública y una parte privada
- ¿Cómo se hace en C?
- La parte pública del módulo se declara en un fichero
.h
(el header)
- La parte privada del módulo se implementa en ficheros
.c
- El módulo con el
main
en general no necesita un header
- El header de un módulo A se incluye (
#include
) en cualquier otro fichero (header o C) que necesita algo público del A (incluyendo el C del propio módulo A).
Estructura de directorios
- Aunque no es necesario, el código puede estructurarse en
directorios, en este laboratorio lo vamos a estructurar así:
/
: en el directorio raiz pondremos un Makefile
y el ejecutable
headers
: directorio con los headers
src
: directorio con el código fuente .c
(src = sources)
obj
: directorio con el código objeto .o
- Importante: las órdenes de compilación tendrán que saber dónde están los
headers
, dónde están los ficheros .c
y dónde se dejan los ficheros .o
- Se lo podemos decir todo a un
Makefile
(el siguiente Makefile
es
un makefile bastante avanzado que sirve para muchos otros proyectos
con la misma estructura, aprovecha para revisarlo):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
CC = gcc
CFLAGS = -Wall -Wextra -Werror -ansi
IFLAGS = -Iheaders
SRC_DIR = src
OBJ_DIR = obj
EXE = generar
# Archivos fuente y objeto
SRCS = $(wildcard $(SRC_DIR)/*.c)
OBJS = $(patsubst $(SRC_DIR)/%.c, $(OBJ_DIR)/%.o, $(SRCS))
# Regla principal
all: $(EXE)
# Regla para enlazar el ejecutable
$(EXE): $(OBJS)
$(CC) $(CFLAGS) -o $@ $^
# Regla para compilar cada archivo .c en un .o
$(OBJ_DIR)/%.o: $(SRC_DIR)/%.c
$(CC) $(CFLAGS) $(IFLAGS) -c $< -o $@
# Reglas de limpieza
clean:
rm -f $(OBJ_DIR)/*.o
veryclean: clean
rm -f $(EXE)
# Regla para crear directorios necesarios si no están creados
prepare:
mkdir -p $(OBJ_DIR)
# Lista de reglas que no generan ficheros
.PHONY: all clean prepare
|
#define
s públicos
- Tipos públicos
- Declaración de variables globales públicas pero NO SU DEFINICIóN
- ¿Cómo se declara una variable?
1
|
extern unsigned long int x;
|
- La palabra reservada
extern
dice existe una variable (o función) que está definida en otro fichero
- Las declaraciones (se llaman también cabeceras) de las funciones públicas del módulo
- ¿Cómo se declara una función? (simplemente se quita su cuerpo, lo que hay entre las llaves)
1
|
unsigned long int media(unsigned long int *a, size_t n);
|
- Nota: se puede poner
extern
delante de la cabecera de una función pero es redundante
- Dobles inclusiones: Si un módulo A necesita el módulo B y el D y a su vez el módulo B necesita el módulo D tendremos el siguiente problema:
- En
a.c
y a.h
tendremos #include
de b.h
y d.h
- En
b.c
y b.h
tendremos #include
de d.h
- Por lo tanto, desde A estaríamos, probablemente, incluyendo dos veces el header de D (
d.h
)
- Para evitarlo los programadores de C tienen una convención: cada header evita ser incluido dos veces usando la directiva
#ifndef
(if not defined)
- Veamos el ejemplo con el módulo
medialu
y lo aplicas tú a lcg
:
1
2
3
4
5
6
7
8
|
#ifndef MEDIALU_H
#define MEDIALU_H
#include<stddef.h>
extern unsigned long int media(unsigned long int *a, size_t n);
#endif /* medialu.h incluido. */
|
- Implementa todo estructurando el programa inicial en los módulos exigidos
- Ejecuta
make
- Ejecuta el programa de esta forma
- ¿Cuál es la media del generador?
- ¿Cómo podrías evitar el potencial overflow de la variable
sum
? Ideas:
- Te podrías salir de Ansi C y buscar tipos muy grandes como por
ejemplo…
__int128
- ¿Y si no te dejan salirte de Ansi C? ¿Podrías hacer a mano un tipo
capaz de representar un unsigned int de 128 bits? ¿Y más grande aún?
- Pista: puedes usar un array de
unsigned long int
haciendo
que cada elemento del array sea un dígito en base 2^64 :o