Semana 5 — Memoria dinámica
Objetivos
- Reservar y liberar memoria con
mallocyfree. - Decidir quién es el owner de cada bloque de memoria.
- Encontrar fugas y accesos inválidos con ASan y valgrind.
Teoría — Memoria dinámica: malloc, free, ownership, ASan y valgrind
Lectura: Apuntes, semana 5.
Temas:
- Lo que la pila no puede: un tamaño que solo se conoce al ejecutar y datos que deben sobrevivir a la función.
- El heap en el modelo de entornos.
malloc,calloc,realloc,freeysizeof. - Revisar el resultado de
malloc. Una función que devuelve un bloque y quien llama lo libera. - Ownership: cada bloque tiene un responsable de liberarlo, y la firma debe decir quién es.
- Errores típicos: fuga, usar después de liberar, liberar dos veces y salirse del bloque.
- Leer un informe de ASan y uno de valgrind.
Laboratorio — Imágenes de cualquier tamaño y cacería de fugas
Sesión 1:
- Cambiar el módulo de imágenes para que reserve la memoria según el tamaño del archivo, con
image_loadeimage_free. - Escribir
str_duplicateystr_concat, que devuelven memoria nueva, con pruebas.
Sesión 2:
3. Cacería de errores: cinco programas dados con fugas y accesos inválidos. Para cada uno, explicar el informe de la herramienta y corregirlo.
4. Escribir un arreglo que crece con realloc, y usarlo para leer un archivo de cualquier largo.
Preguntas de repaso
- ¿Por qué
int *p = malloc(10);probablemente no reserva 10 enteros? - ¿Qué error tiene
free(p); printf("%d", *p);? ¿Cómo lo reporta ASan? - Una función devuelve una cadena nueva. ¿Quién debe liberarla? ¿Cómo se lo dicen a quien la usa?
- ¿Por qué
p = realloc(p, n);puede causar una fuga?
Código de la semana
Compilar y probar dentro del contenedor del curso con make y make test.
Ejemplos (10)Errores (13)Pruebas (3)Laboratorio (24)Compilación (1)
Ejemplos
Los programas que usan los apuntes y la clase.
// calloc reserva n elementos y los deja en cero.
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int n = 5;
int *counts = calloc(n, sizeof *counts);
if (counts == NULL) {
return 1;
}
counts[2] = counts[2] + 1;
for (int i = 0; i < n; i++) {
printf("%d ", counts[i]);
}
printf("\n");
free(counts);
return 0;
}// Cuenta las llamadas a realloc al agregar 20000 enteros, con dos reglas.
#include <stdio.h>
#include <stdlib.h>
#define TOTAL 20000
// factor 0: crece de uno en uno. factor 2: duplica la capacidad.
// Devuelve cuántas veces llamó a realloc, o -1 si no hubo memoria.
long fill(int factor) {
int *items = NULL;
size_t capacity = 0;
long calls = 0;
for (size_t count = 0; count < TOTAL; count++) {
if (count == capacity) {
size_t wanted = factor == 0 || capacity == 0 ? capacity + 1 : capacity * factor;
int *bigger = realloc(items, wanted * sizeof *items);
if (bigger == NULL) {
free(items);
return -1;
}
items = bigger;
capacity = wanted;
calls++;
}
items[count] = (int) count;
}
free(items);
return calls;
}
int main(void) {
printf("de uno en uno: %ld llamadas\n", fill(0));
printf("duplicando: %ld llamadas\n", fill(2));
return 0;
}// El struct ya no lleva los píxeles adentro: solo un puntero a ellos.
#include <stdio.h>
#include "lab/image.h"
int main(void) {
printf("sizeof(struct image) = %zu\n", sizeof(struct image));
return 0;
}// Pide un bloque imposible, para ver cómo avisa malloc.
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
int main(void) {
size_t huge = SIZE_MAX / 2;
char *block = malloc(huge);
if (block == NULL) {
printf("malloc devolvió NULL: no hay memoria\n");
return 1;
}
printf("Se reservó el bloque\n");
free(block);
return 0;
}// Una función reserva un bloque, y el bloque sobrevive a su marco.
#include <stdio.h>
#include <stdlib.h>
// Devuelve un bloque nuevo con first, first + 1, ... (n elementos), o NULL si no hay
// memoria. Quien llama es owner del bloque, y debe liberarlo con free.
int *range_new(int first, int n) {
int *block = malloc(n * sizeof *block);
if (block == NULL) {
return NULL;
}
for (int i = 0; i < n; i++) {
block[i] = first + i;
}
return block;
}
int main(void) {
int *numbers = range_new(10, 4);
if (numbers == NULL) {
fprintf(stderr, "No hay memoria\n");
return 1;
}
for (int i = 0; i < 4; i++) {
printf("%d ", numbers[i]);
}
printf("\n");
free(numbers);
return 0;
}// realloc cambia el tamaño de un bloque y conserva lo que tenía.
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int *values = malloc(2 * sizeof *values);
if (values == NULL) {
return 1;
}
values[0] = 10;
values[1] = 20;
int *bigger = realloc(values, 4 * sizeof *values);
if (bigger == NULL) {
// values sigue siendo válido: su owner todavía debe liberarlo.
free(values);
return 1;
}
values = bigger;
values[2] = 30;
values[3] = 40;
for (int i = 0; i < 4; i++) {
printf("%d ", values[i]);
}
printf("\n");
free(values);
return 0;
}// Lee n y luego n números, y los escribe al revés. El tamaño se conoce al ejecutar.
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int n;
if (scanf("%d", &n) != 1 || n < 1) {
fprintf(stderr, "Entrada inválida\n");
return 1;
}
int *values = malloc(n * sizeof *values);
if (values == NULL) {
fprintf(stderr, "No hay memoria para %d números\n", n);
return 1;
}
for (int i = 0; i < n; i++) {
if (scanf("%d", &values[i]) != 1) {
fprintf(stderr, "Entrada inválida\n");
free(values);
return 1;
}
}
for (int i = n - 1; i >= 0; i--) {
printf("%d ", values[i]);
}
printf("\n");
free(values);
return 0;
}// Cuánto mide cada cosa. malloc recibe bytes, no elementos.
#include <stdio.h>
#include <stdlib.h>
struct point {
int x;
int y;
};
int main(void) {
int *p = malloc(10 * sizeof *p);
struct point *q = malloc(sizeof *q);
if (p == NULL || q == NULL) {
free(p);
free(q);
return 1;
}
printf("sizeof(int) = %zu\n", sizeof(int));
printf("sizeof p (puntero) = %zu\n", sizeof p);
printf("sizeof *p (un int) = %zu\n", sizeof *p);
printf("sizeof q (puntero) = %zu\n", sizeof q);
printf("sizeof *q (un point) = %zu\n", sizeof *q);
printf("10 enteros = %zu bytes\n", 10 * sizeof *p);
free(p);
free(q);
return 0;
}// Tres firmas, tres respuestas a la pregunta: ¿quién libera?
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// Devuelve una cadena nueva con text en mayúsculas, o NULL si no hay memoria.
// Quien llama es owner del resultado, y debe liberarlo con free.
char *upper_copy(const char *text) {
char *copy = malloc(strlen(text) + 1);
if (copy == NULL) {
return NULL;
}
size_t i = 0;
for (; text[i] != '\0'; i++) {
copy[i] = (char) toupper((unsigned char) text[i]);
}
copy[i] = '\0';
return copy;
}
// Cambia text en su lugar. El owner de text sigue siendo quien llama.
void upper_in_place(char *text) {
for (size_t i = 0; text[i] != '\0'; i++) {
text[i] = (char) toupper((unsigned char) text[i]);
}
}
// Devuelve una de las dos cadenas que recibió. No hay memoria nueva: no se libera.
const char *longer_of(const char *a, const char *b) {
return strlen(a) >= strlen(b) ? a : b;
}
int main(void) {
char *upper = upper_copy("paradero");
if (upper == NULL) {
return 1;
}
printf("%s\n", upper);
free(upper);
char word[] = "ruta";
upper_in_place(word);
printf("%s\n", word);
printf("%s\n", longer_of("Lima", "Arequipa"));
return 0;
}// Un struct que es owner de un bloque: reservar dos veces y liberar dos veces.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct student {
int code;
char *name;
};
// Devuelve un estudiante nuevo, con su propia copia de name, o NULL si no hay memoria.
// Quien llama es owner del resultado, y lo libera con student_free.
struct student *student_new(int code, const char *name) {
struct student *s = malloc(sizeof *s);
if (s == NULL) {
return NULL;
}
s->name = malloc(strlen(name) + 1);
if (s->name == NULL) {
free(s);
return NULL;
}
strcpy(s->name, name);
s->code = code;
return s;
}
// Libera el estudiante y su nombre. Con NULL no hace nada.
void student_free(struct student *s) {
if (s == NULL) {
return;
}
free(s->name);
free(s);
}
int main(void) {
struct student *ana = student_new(101, "Ana");
if (ana == NULL) {
return 1;
}
// Copiar el struct copia el puntero, no el nombre: ahora los dos comparten un bloque.
struct student shallow = *ana;
shallow.name[0] = 'E';
printf("%s %s\n", ana->name, shallow.name);
printf("mismo bloque: %d\n", ana->name == shallow.name);
student_free(ana);
return 0;
}Errores
Ejemplos con errores a propósito: no compilan, fallan o dan un resultado incorrecto. Los apuntes explican cada uno.
// Error: un arreglo local más grande que la pila.
#include <stdio.h>
#define SIZE 4000000
int main(void) {
int values[SIZE];
for (int i = 0; i < SIZE; i++) {
values[i] = i;
}
printf("%d\n", values[SIZE - 1]);
return 0;
}// Error: liberar dos veces. Dos funciones creen ser owners del mismo bloque.
#include <stdio.h>
#include <stdlib.h>
void show_and_free(int *values, int n) {
for (int i = 0; i < n; i++) {
printf("%d ", values[i]);
}
printf("\n");
free(values);
}
int main(void) {
int *values = calloc(3, sizeof *values);
if (values == NULL) {
return 1;
}
show_and_free(values, 3);
free(values);
return 0;
}// Error: free recibe la dirección que dio malloc, no otra dentro del bloque.
#include <stdlib.h>
int main(void) {
int *values = malloc(4 * sizeof *values);
if (values == NULL) {
return 1;
}
values++;
free(values);
return 0;
}// Error: fuga de memoria. Nadie libera lo que devuelve range_new.
#include <stdio.h>
#include <stdlib.h>
int *range_new(int first, int n) {
int *block = malloc(n * sizeof *block);
if (block == NULL) {
return NULL;
}
for (int i = 0; i < n; i++) {
block[i] = first + i;
}
return block;
}
int main(void) {
for (int round = 0; round < 3; round++) {
int *numbers = range_new(round, 10);
if (numbers == NULL) {
return 1;
}
printf("%d\n", numbers[9]);
}
return 0;
}// Error: devuelve la dirección de una local. Su marco ya no existe al volver.
#include <stdio.h>
int *range_local(int first) {
int block[4];
for (int i = 0; i < 4; i++) {
block[i] = first + i;
}
return block;
}
int main(void) {
int *numbers = range_local(10);
printf("%d\n", numbers[0]);
return 0;
}// Error: salirse de un arreglo local, no de un bloque del heap.
#include <stdio.h>
int main(void) {
int values[4] = {1, 2, 3, 4};
int total = 0;
for (int i = 0; i <= 4; i++) {
total = total + values[i];
}
printf("%d\n", total > 0);
return 0;
}// Error: salirse del bloque por uno.
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int n = 4;
int *values = malloc(n * sizeof *values);
if (values == NULL) {
return 1;
}
for (int i = 0; i <= n; i++) {
values[i] = i * i;
}
printf("%d\n", values[n - 1]);
free(values);
return 0;
}// Error: p = realloc(p, n). Si realloc falla, se pierde la única dirección del bloque.
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
int main(void) {
size_t huge = SIZE_MAX / 2;
int *p = malloc(10 * sizeof *p);
if (p == NULL) {
return 1;
}
p = realloc(p, huge);
if (p == NULL) {
printf("realloc devolvió NULL\n");
return 1;
}
free(p);
return 0;
}// Error: malloc recibe bytes. 10 bytes no alcanzan para 10 enteros.
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int *p = malloc(10);
if (p == NULL) {
return 1;
}
for (int i = 0; i < 10; i++) {
p[i] = i;
}
printf("%d\n", p[9]);
free(p);
return 0;
}// Error: seguir usando la dirección de antes de realloc.
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int *values = calloc(2, sizeof *values);
if (values == NULL) {
return 1;
}
int *first = values;
int *bigger = realloc(values, 100000 * sizeof *values);
if (bigger == NULL) {
free(values);
return 1;
}
values = bigger;
printf("%d\n", *first);
free(values);
return 0;
}// strdup no está en C17: con -std=c17, stdlib y string.h no la declaran.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(void) {
char *copy = strdup("Lima");
if (copy == NULL) {
return 1;
}
printf("%s\n", copy);
free(copy);
return 0;
}// Error: decidir con un valor que nadie escribió. malloc no limpia el bloque.
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int *counts = malloc(3 * sizeof *counts);
if (counts == NULL) {
return 1;
}
counts[0] = 5;
counts[2] = 5;
if (counts[1] > 0) {
printf("hay datos\n");
} else {
printf("no hay datos\n");
}
free(counts);
return 0;
}// Error: usar un bloque después de liberarlo.
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int *p = malloc(10 * sizeof *p);
if (p == NULL) {
return 1;
}
p[0] = 7;
free(p);
printf("%d\n", *p);
return 0;
}Pruebas
Comprueban lo que afirman los apuntes; se ejecutan con make test.
"""Comprueba los programas de la clase y lo que los apuntes dicen de sus errores."""
import pathlib
import subprocess
import tempfile
import unittest
HERE = pathlib.Path(__file__).parent
BUILD = HERE / "build"
FLAGS = ["-Wall", "-Wextra", "-std=c17", "-g", "-fsanitize=address,undefined"]
VALGRIND_FLAGS = ["-Wall", "-Wextra", "-std=c17", "-g", "-O0"]
PROGRAMS = ["reverse_numbers", "range_block", "sizes", "calloc_zero", "strings_owner",
"realloc_grow", "growth_count", "image_size", "no_memory"]
# Programa de errors/ y lo que debe decir su informe con sanitizers.
SANITIZER_ERRORS = {
"big_local": "stack-overflow",
"short_block": "heap-buffer-overflow",
"leak": "LeakSanitizer: detected memory leaks",
"use_after_free": "heap-use-after-free",
"double_free": "attempting double-free",
"off_by_one": "heap-buffer-overflow",
"free_middle": "attempting free on address which was not malloc()-ed",
"local_overflow": "stack-buffer-overflow",
"stale_pointer": "heap-use-after-free",
"realloc_lost": "allocation-size-too-big",
}
# Programa de errors/ y lo que debe decir valgrind.
VALGRIND_ERRORS = {
"leak": "definitely lost: 120 bytes in 3 blocks",
"use_after_free": "Invalid read of size 4",
"double_free": "Invalid free()",
"off_by_one": "Invalid write of size 4",
"uninit_read": "Conditional jump or move depends on uninitialised value(s)",
"stale_pointer": "Invalid read of size 4",
"realloc_lost": "definitely lost: 40 bytes in 1 blocks",
}
# Programa de la cacería y lo que reporta cada herramienta en la versión sin corregir.
HUNT_ERRORS = {
"longest_word": ("LeakSanitizer", "definitely lost"),
"seat_list": ("heap-use-after-free", "Invalid read"),
"route_backup": ("attempting double-free", "Invalid free()"),
"grade_histogram": ("heap-buffer-overflow", "Invalid read"),
"passing_groups": (None, "Conditional jump"),
}
def run(command, stdin=None):
done = subprocess.run(
command, cwd=HERE, text=True, encoding="utf-8", input=stdin,
stdout=subprocess.PIPE, stderr=subprocess.STDOUT, timeout=60,
)
return done.stdout, done.returncode
def with_sanitizers(source, stdin=None):
with tempfile.TemporaryDirectory() as folder:
binary = pathlib.Path(folder) / "program"
built, code = run(["gcc", *FLAGS, str(source), "-o", str(binary)])
if code != 0:
raise AssertionError(built)
return run([str(binary)], stdin)
def with_valgrind(source):
with tempfile.TemporaryDirectory() as folder:
binary = pathlib.Path(folder) / "program"
built, code = run(["gcc", *VALGRIND_FLAGS, str(source), "-o", str(binary)])
if code != 0:
raise AssertionError(built)
return run(["valgrind", "--leak-check=full", str(binary)])
class Programs(unittest.TestCase):
def test_every_program_compiles_without_warnings(self):
for name in [*PROGRAMS, "student"]:
with self.subTest(program=name):
output, code = run(["gcc", *FLAGS, "-Werror", "-c", f"{name}.c", "-o", "/dev/null"])
self.assertEqual(code, 0, output)
def test_examples_end_without_errors(self):
# Con sanitizers: una fuga o un acceso inválido cambiaría el código de salida.
for name in ["range_block", "sizes", "calloc_zero", "strings_owner", "student",
"realloc_grow", "growth_count", "image_size"]:
with self.subTest(program=name):
output, code = run([str(BUILD / name)])
self.assertEqual(code, 0, output)
def test_examples_are_clean_in_valgrind(self):
for name in ["range_block", "calloc_zero", "strings_owner", "student", "realloc_grow"]:
with self.subTest(program=name):
output, _ = with_valgrind(HERE / f"{name}.c")
self.assertIn("ERROR SUMMARY: 0 errors", output)
self.assertIn("no leaks are possible", output)
def test_reverse_reads_any_count(self):
numbers = " ".join(str(i) for i in range(5000))
output, code = run([str(BUILD / "reverse_numbers")], f"5000 {numbers}\n")
self.assertEqual(code, 0)
self.assertEqual(output.split(), [str(i) for i in reversed(range(5000))])
def test_reverse_rejects_bad_input_without_leaks(self):
for text in ("x\n", "0\n", "-2\n", "3 1 2\n"):
with self.subTest(text=text):
output, code = run([str(BUILD / "reverse_numbers")], text)
self.assertEqual(code, 1)
self.assertEqual(output.strip(), "Entrada inválida")
def test_no_memory_reports_null_without_sanitizers(self):
output, code = run([str(BUILD / "no_memory")])
self.assertEqual(code, 1)
self.assertIn("devolvió NULL", output)
class Errors(unittest.TestCase):
def test_sanitizers_name_each_error(self):
for name, marker in SANITIZER_ERRORS.items():
with self.subTest(program=name):
output, code = with_sanitizers(HERE / "errors" / f"{name}.c")
self.assertNotEqual(code, 0)
self.assertIn(marker, output)
def test_valgrind_names_each_error(self):
for name, marker in VALGRIND_ERRORS.items():
with self.subTest(program=name):
output, _ = with_valgrind(HERE / "errors" / f"{name}.c")
self.assertIn(marker, output)
def test_asan_does_not_see_uninitialized_reads(self):
output, code = with_sanitizers(HERE / "errors" / "uninit_read.c")
self.assertEqual(code, 0)
self.assertNotIn("ERROR", output)
def test_valgrind_does_not_see_local_overflows(self):
output, _ = with_valgrind(HERE / "errors" / "local_overflow.c")
self.assertIn("ERROR SUMMARY: 0 errors", output)
def test_strdup_is_not_declared_in_c17(self):
output, code = run(["gcc", *VALGRIND_FLAGS, "-c", "errors/strdup_c17.c", "-o", "/dev/null"])
self.assertNotEqual(code, 0)
self.assertIn("strdup", output)
@unittest.skipUnless((HERE / "solution").exists(), "falta la solución")
class LabAsPublished(unittest.TestCase):
"""Solo con la carpeta de la solución, antes de entregar el laboratorio: ustedes van a
corregir lab/hunt, y estas pruebas ya no tendrían sentido en su copia."""
def test_hunt_compiles_without_warnings(self):
for name in HUNT_ERRORS:
with self.subTest(program=name):
output, code = run(["gcc", *FLAGS, "-Werror", "-c", f"lab/hunt/{name}.c",
"-o", "/dev/null"])
self.assertEqual(code, 0, output)
def test_each_hunt_program_has_the_error_it_should(self):
for name, (with_asan, with_vg) in HUNT_ERRORS.items():
source = HERE / "lab" / "hunt" / f"{name}.c"
with self.subTest(program=name, tool="sanitizers"):
output, code = with_sanitizers(source)
if with_asan is None:
self.assertEqual(code, 0, output)
else:
self.assertNotEqual(code, 0)
self.assertIn(with_asan, output)
with self.subTest(program=name, tool="valgrind"):
self.assertIn(with_vg, with_valgrind(source)[0])
def test_starter_and_solution_compile_without_warnings(self):
for folder in ("lab", "solution"):
for name in ("image.c", "str_tools.c", "int_array.c", "image_tests.c", "str_tests.c",
"array_tests.c"):
with self.subTest(file=f"{folder}/{name}"):
output, code = run(["gcc", *FLAGS, "-Werror", "-Ilab", "-c", f"{folder}/{name}",
"-o", "/dev/null"])
self.assertEqual(code, 0, output)
for name in ("filters.c", "image_demo.c", "sum_file.c"):
with self.subTest(file=f"lab/{name}"):
output, code = run(["gcc", *FLAGS, "-Werror", "-Ilab", "-c", f"lab/{name}",
"-o", "/dev/null"])
self.assertEqual(code, 0, output)
if __name__ == "__main__":
unittest.main()"""Pruebas del laboratorio. LAB_DIR dice dónde está el código: lab o solution.
Cada prueba compila un main propio con los archivos de esa carpeta, y lo corre con
sanitizers: una fuga o un acceso inválido hace fallar la prueba, aunque el resultado
parezca correcto.
"""
import os
import pathlib
import re
import resource
import subprocess
import tempfile
import unittest
HERE = pathlib.Path(__file__).parent
LAB_DIR = os.environ.get("LAB_DIR", "solution")
SOURCE = HERE / LAB_DIR
HEADERS = HERE / "lab"
FLAGS = ["-Wall", "-Wextra", "-std=c17", "-g", "-fsanitize=address,undefined"]
VALGRIND_FLAGS = ["-Wall", "-Wextra", "-std=c17", "-g", "-O0"]
# Con este límite, un archivo sin cerrar se nota después de pocas lecturas. Los sanitizers
# no lo reportan como fuga, porque la biblioteca estándar conserva una lista de archivos abiertos.
OPEN_FILES_LIMIT = 32
# Hace fallar la llamada número fail_at a malloc, calloc o realloc. Solo para estas pruebas.
FAILING_ALLOCATOR = """
#include <stdlib.h>
void *__real_malloc(size_t size);
void *__real_calloc(size_t count, size_t size);
void *__real_realloc(void *block, size_t size);
static int calls = 0;
static int fail_at = 0;
static int next_fails(void) { calls++; return calls == fail_at; }
void *__wrap_malloc(size_t size) { return next_fails() ? NULL : __real_malloc(size); }
void *__wrap_calloc(size_t count, size_t size) {
return next_fails() ? NULL : __real_calloc(count, size);
}
void *__wrap_realloc(void *block, size_t size) {
return next_fails() ? NULL : __real_realloc(block, size);
}
"""
WRAP = ["-Wl,--wrap=malloc", "-Wl,--wrap=calloc", "-Wl,--wrap=realloc"]
def short_report(text):
"""Deja lo útil de un informe de los sanitizers: el tipo de error y las líneas propias."""
if "ERROR:" not in text and "runtime error" not in text:
return text.strip()
useful = [line for line in text.splitlines()
if "ERROR:" in line or "SUMMARY:" in line or "runtime error" in line
or re.search(r"\b(driver|image|filters|str_tools|int_array)\.c:\d+", line)]
return "\n".join(line.strip() for line in useful[:12])
def limit_open_files():
resource.setrlimit(resource.RLIMIT_NOFILE, (OPEN_FILES_LIMIT, OPEN_FILES_LIMIT))
def compile_and_run(main_source, sources, files=None, extra=(), stdin=None, setup=None):
with tempfile.TemporaryDirectory() as name:
folder = pathlib.Path(name)
driver = folder / "driver.c"
driver.write_text(main_source, encoding="utf-8")
for file_name, content in (files or {}).items():
(folder / file_name).write_text(content, encoding="utf-8")
binary = folder / "driver"
built = subprocess.run(
["gcc", *FLAGS, f"-I{HEADERS}", str(driver), *map(str, sources), *extra, "-o", str(binary)],
text=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
)
if built.returncode != 0:
raise AssertionError(f"No compila:\n{built.stdout}")
done = subprocess.run(
[str(binary)], cwd=folder, text=True, encoding="utf-8", input=stdin, preexec_fn=setup,
stdout=subprocess.PIPE, stderr=subprocess.STDOUT, timeout=60,
)
return done.stdout, done.returncode
class LabTest(unittest.TestCase):
maxDiff = None
def run_clean(self, source, sources, files=None, extra=(), context="", setup=None):
"""Corre el programa y exige que termine bien, sin informes de los sanitizers."""
out, code = compile_and_run(source, sources, files, extra, setup=setup)
if code != 0:
self.fail(f"{context}\nEl programa falló:\n{short_report(out)}")
return out
def same(self, got, wanted, context):
if got != wanted:
self.fail(f"{context}\n esperado: {wanted}\n obtuvo: {got}")
def image_sources():
return [SOURCE / "image.c", HERE / "lab" / "filters.c"]
def image_main(body):
return ('#include <stdio.h>\n#include "filters.h"\n'
"static void dump(const struct image *img) {\n"
' if (img == NULL) { printf("NULL\\n"); return; }\n'
' printf("%d %d %d", img->width, img->height, img->max_value);\n'
" for (int i = 0; i < img->width * img->height; i++) {\n"
' printf(" %d", img->pixels[i]);\n }\n printf("\\n");\n}\n'
"int main(void) {\n" + body + "\nreturn 0;\n}\n")
def pgm(width, height, max_value, values):
numbers = " ".join(map(str, values))
return f"P2\n{width} {height}\n{max_value}\n{numbers}\n"
@unittest.skipUnless((SOURCE / "image.c").exists(), f"no está {LAB_DIR}/image.c")
class Images(LabTest):
def load(self, content, context):
"""Carga content con image_load. Devuelve lo que escribe dump, sin espacios al final."""
body = 'struct image *img = image_load("in.pgm"); dump(img); image_free(img);'
return self.run_clean(image_main(body), image_sources(), {"in.pgm": content},
context=context).strip()
def test_new_fills_every_pixel(self):
body = "struct image *img = image_new(3, 2, 200, 7); dump(img); image_free(img);"
out = self.run_clean(image_main(body), image_sources(),
context="image_new(3, 2, 200, 7)")
self.same(out.strip(), "3 2 200 7 7 7 7 7 7", "image_new(3, 2, 200, 7)")
def test_new_rejects_bad_data(self):
cases = ["0, 2, 255, 0", "3, 0, 255, 0", "-3, 2, 255, 0", "3, -2, 255, 0", "3, 2, 0, 0",
"3, 2, 256, 0", "3, 2, 255, 256", "3, 2, 255, -1"]
for case in cases:
with self.subTest(args=case):
body = f"dump(image_new({case}));"
out = self.run_clean(image_main(body), image_sources(),
context=f"image_new({case})")
self.same(out.strip(), "NULL", f"image_new({case}) debe devolver NULL")
def test_new_rejects_a_size_that_does_not_fit(self):
# width * height no cabe en un int. Hay que notarlo antes de multiplicar.
body = "dump(image_new(50000, 50000, 255, 0));"
out = self.run_clean(image_main(body), image_sources(),
context="image_new(50000, 50000, 255, 0)")
self.same(out.strip(), "NULL", "image_new(50000, 50000, 255, 0) debe devolver NULL")
def test_free_accepts_null(self):
self.run_clean(image_main("image_free(NULL);"), image_sources(), context="image_free(NULL)")
def test_load_small_image_with_comment(self):
content = "P2\n# un comentario\n3 2\n255\n0 10 20\n30 40 50\n"
self.same(self.load(content, "imagen de 3 x 2"), "3 2 255 0 10 20 30 40 50",
"imagen de 3 x 2 con un comentario")
def test_load_keeps_the_rows_in_order(self):
# No es cuadrada: descubre si se confundieron width y height.
content = pgm(2, 3, 9, [1, 2, 3, 4, 5, 6])
self.same(self.load(content, "imagen de 2 x 3"), "2 3 9 1 2 3 4 5 6", "imagen de 2 x 3")
def test_load_larger_than_the_old_limit(self):
width, height = 300, 200
values = [(x * 7 + y * 11) % 256 for y in range(height) for x in range(width)]
expected = " ".join(map(str, [width, height, 255, *values]))
got = self.load(pgm(width, height, 255, values), "imagen de 300 x 200")
if got != expected:
self.fail("La imagen de 300 x 200 no se cargó bien: sus píxeles no coinciden.")
def test_load_the_range_image(self):
body = ('struct image *img = image_load("' + str(HERE / "lab/images/range.pgm") + '");\n'
'if (img != NULL) { printf("%d %d\\n", img->width, img->height); }\n'
"image_free(img);")
out = self.run_clean(image_main(body), image_sources(), context="lab/images/range.pgm")
self.same(out.strip(), "96 28", "ancho y alto de lab/images/range.pgm")
def test_load_failures_release_everything(self):
cases = {
"no es P2": "P5\n2 1\n255\n0 0\n",
"archivo vacío": "",
"sin alto": "P2\n2\n",
"ancho 0": pgm(0, 1, 255, []),
"max_value mayor que 255": pgm(1, 1, 256, [0]),
"faltan píxeles": "P2\n3 2\n255\n1 2 3 4\n",
"píxel mayor que max_value": pgm(2, 1, 9, [1, 10]),
"una letra entre los píxeles": "P2\n2 1\n255\n1 x\n",
"el tamaño no cabe en un int": "P2\n50000 50000\n255\n1\n",
"un número que no cabe en un int": "P2\n99999999999 1\n255\n1\n",
}
for label, content in cases.items():
with self.subTest(label):
self.same(self.load(content, label), "NULL", f"{label}: debe devolver NULL")
def test_load_missing_file(self):
body = 'dump(image_load("no_existe.pgm"));'
out = self.run_clean(image_main(body), image_sources(), context="archivo que no existe")
self.same(out.strip(), "NULL", "un archivo que no existe debe dar NULL")
def test_load_closes_the_file(self):
body = ("for (int i = 0; i < 100; i++) {\n"
' struct image *good = image_load("good.pgm");\n'
' if (good == NULL) { printf("falló la lectura %d\\n", i); return 1; }\n'
" image_free(good);\n"
' image_free(image_load("bad.pgm"));\n'
"}")
self.run_clean(image_main(body), image_sources(),
{"good.pgm": pgm(1, 1, 9, [1]), "bad.pgm": "P2\n2 1\n9\n1\n"},
context="100 lecturas seguidas: ¿se cierra el archivo en todos los caminos?",
setup=limit_open_files)
def test_save_and_load_again(self):
body = ("const int values[] = {1, 2, 3, 4, 5, 6};\n"
"struct image *img = image_from_values(3, 2, 9, values);\n"
'if (img == NULL || !image_save(img, "out.pgm")) { return 1; }\n'
'struct image *again = image_load("out.pgm");\n'
"dump(again);\n"
"image_free(again);\nimage_free(img);")
out = self.run_clean(image_main(body), image_sources(), context="guardar y volver a cargar")
self.same(out.strip(), "3 2 9 1 2 3 4 5 6", "guardar y volver a cargar")
def test_filters_work_on_any_size(self):
body = ("struct image *img = image_new(70, 3, 255, 200);\n"
"if (img == NULL) { return 1; }\n"
"img->pixels[0] = 10;\n"
"flip_horizontal(img);\ninvert(img);\n"
'printf("%d %d\\n", img->pixels[69], img->pixels[0]);\n'
"image_free(img);")
out = self.run_clean(image_main(body), image_sources(), context="filtros en 70 x 3")
self.same(out.strip(), "245 55", "flip_horizontal e invert en una imagen de 70 x 3")
def out_of_memory(self, call, context):
source = FAILING_ALLOCATOR + image_main(
"int nulls = 0;\n"
"for (fail_at = 1; ; fail_at++) {\n"
" calls = 0;\n"
f" struct image *img = {call};\n"
" if (img == NULL) { nulls++; }\n"
" image_free(img);\n"
" if (calls < fail_at) { break; }\n"
"}\n"
'printf("%d\\n", nulls);')
out = self.run_clean(source, image_sources(), {"in.pgm": pgm(2, 1, 9, [1, 2])}, WRAP,
context=f"{context}, cuando malloc devuelve NULL")
numbers = re.findall(r"^\d+$", out, re.M)
if not numbers or int(numbers[-1]) < 1:
self.fail(f"{context}: debe devolver NULL cuando malloc devuelve NULL.")
def test_new_when_there_is_no_memory(self):
self.out_of_memory("image_new(2, 1, 9, 0)", "image_new")
def test_load_when_there_is_no_memory(self):
self.out_of_memory('image_load("in.pgm")', "image_load")
def str_main(body):
return ('#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include "str_tools.h"\n'
"int main(void) {\n" + body + "\nreturn 0;\n}\n")
@unittest.skipUnless((SOURCE / "str_tools.c").exists(), f"no está {LAB_DIR}/str_tools.c")
class Strings(LabTest):
def result_of(self, call):
body = (f"char *result = {call};\n"
'if (result == NULL) { printf("NULL\\n"); return 0; }\n'
'printf("[%s]\\n", result);\nfree(result);')
return self.run_clean(str_main(body), [SOURCE / "str_tools.c"], context=call).strip()
def test_duplicate(self):
for text in ("Lima", "", "a", "con ñ y tilde á"):
with self.subTest(text=text):
call = f'str_duplicate("{text}")'
self.same(self.result_of(call), f"[{text}]", call)
def test_duplicate_is_a_new_block(self):
body = ('char original[] = "Lima";\nchar *copy = str_duplicate(original);\n'
"if (copy == NULL) { return 1; }\n"
"if (copy == original) { return 2; }\n"
"copy[0] = 'K';\n"
'printf("%s %s\\n", original, copy);\nfree(copy);')
out = self.run_clean(str_main(body), [SOURCE / "str_tools.c"],
context="str_duplicate debe devolver otro bloque")
self.same(out.strip(), "Lima Kima", "cambiar la copia no debe cambiar el original")
def test_concat(self):
cases = [("Puno", " y Tacna"), ("", "Ica"), ("Ica", ""), ("", "")]
for first, second in cases:
with self.subTest(first=first, second=second):
call = f'str_concat("{first}", "{second}")'
self.same(self.result_of(call), f"[{first}{second}]", call)
def test_concat_with_itself_and_keeps_the_arguments(self):
body = ('char word[] = "ab";\nchar *joined = str_concat(word, word);\n'
"if (joined == NULL) { return 1; }\n"
'printf("%s %s\\n", word, joined);\nfree(joined);')
out = self.run_clean(str_main(body), [SOURCE / "str_tools.c"],
context="str_concat(word, word)")
self.same(out.strip(), "ab abab", "str_concat(word, word)")
def test_no_memory(self):
for call in ('str_duplicate("Lima")', 'str_concat("Li", "ma")'):
with self.subTest(call=call):
source = FAILING_ALLOCATOR + str_main(
"fail_at = 1;\n"
f"char *result = {call};\n"
'printf("%s\\n", result == NULL ? "NULL" : "otra");\n'
"free(result);")
out = self.run_clean(source, [SOURCE / "str_tools.c"], extra=WRAP,
context=f"{call}, cuando malloc devuelve NULL")
self.same(out.strip(), "NULL", f"{call} debe devolver NULL si no hay memoria")
# Lo que debe escribir cada programa de la cacería ya corregido.
HUNT_OUTPUT = {
"longest_word": "CAJAMARCA\n",
"seat_list": "primero: 1, último: 103\n",
"route_backup": "ruta: Kallao, respaldo: Callao\n",
"grade_histogram": "7: 1\n11: 1\n14: 2\n20: 2\n",
"passing_groups": "grupos que llegan a 30: 1\n",
}
@unittest.skipUnless((SOURCE / "hunt").exists(), f"no está {LAB_DIR}/hunt")
class Hunt(LabTest):
def check_with_sanitizers(self, name):
source = (SOURCE / "hunt" / f"{name}.c").read_text(encoding="utf-8")
out, code = compile_and_run(source, [])
if code != 0 or "ERROR:" in out or "runtime error" in out:
self.fail(f"{name}.c aún tiene un error:\n{short_report(out)}")
self.same(out, HUNT_OUTPUT[name], f"lo que escribe {name}.c")
def check_with_valgrind(self, name):
with tempfile.TemporaryDirectory() as folder:
binary = pathlib.Path(folder) / name
subprocess.run(
["gcc", *VALGRIND_FLAGS, str(SOURCE / "hunt" / f"{name}.c"), "-o", str(binary)],
check=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
)
done = subprocess.run(
["valgrind", "-q", "--leak-check=full", "--error-exitcode=9", str(binary)],
text=True, encoding="utf-8", stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
timeout=60,
)
if done.returncode != 0:
lines = [line for line in done.stdout.splitlines() if "==" in line][:6]
self.fail(f"valgrind todavía reporta errores en {name}.c:\n" + "\n".join(lines))
def test_longest_word(self):
self.check_with_sanitizers("longest_word")
self.check_with_valgrind("longest_word")
def test_seat_list(self):
self.check_with_sanitizers("seat_list")
self.check_with_valgrind("seat_list")
def test_route_backup(self):
self.check_with_sanitizers("route_backup")
self.check_with_valgrind("route_backup")
def test_grade_histogram(self):
self.check_with_sanitizers("grade_histogram")
self.check_with_valgrind("grade_histogram")
def test_passing_groups(self):
self.check_with_sanitizers("passing_groups")
self.check_with_valgrind("passing_groups")
def array_main(body):
return ('#include <stdio.h>\n#include "int_array.h"\n'
"int main(void) {\n" + body + "\nreturn 0;\n}\n")
@unittest.skipUnless((SOURCE / "int_array.c").exists(), f"no está {LAB_DIR}/int_array.c")
class GrowingArray(LabTest):
def read(self, content, context):
body = ('struct int_array *a = int_array_read_file("in.txt");\n'
'if (a == NULL) { printf("NULL\\n"); return 0; }\n'
'printf("%zu", a->count);\n'
'for (size_t i = 0; i < a->count; i++) { printf(" %d", a->items[i]); }\n'
'printf("\\n");\nint_array_free(a);')
return self.run_clean(array_main(body), [SOURCE / "int_array.c"],
{"in.txt": content}, context=context).strip()
def test_new_is_empty(self):
body = ("struct int_array *a = int_array_new();\n"
"if (a == NULL) { return 1; }\n"
'printf("%zu\\n", a->count);\nint_array_free(a);')
out = self.run_clean(array_main(body), [SOURCE / "int_array.c"], context="int_array_new")
self.same(out.strip(), "0", "un arreglo nuevo tiene count en 0")
def test_free_accepts_null(self):
self.run_clean(array_main("int_array_free(NULL);"), [SOURCE / "int_array.c"],
context="int_array_free(NULL)")
def test_push_keeps_everything(self):
body = ("struct int_array *a = int_array_new();\n"
"if (a == NULL) { return 1; }\n"
"for (int i = 0; i < 1000; i++) { if (!int_array_push(a, i * 3)) { return 2; } }\n"
"long sum = 0;\n"
"for (size_t i = 0; i < a->count; i++) {\n"
" if (a->items[i] != (int) i * 3) { return 3; }\n"
" sum += a->items[i];\n}\n"
'printf("%zu %ld\\n", a->count, sum);\nint_array_free(a);')
out = self.run_clean(array_main(body), [SOURCE / "int_array.c"], context="1000 int_array_push")
self.same(out.strip(), "1000 1498500", "count y suma después de 1000 int_array_push")
def test_capacity_doubles(self):
source = FAILING_ALLOCATOR + array_main(
"struct int_array *a = int_array_new();\n"
"if (a == NULL) { return 1; }\n"
"calls = 0;\n"
"for (int i = 0; i < 10000; i++) { if (!int_array_push(a, i)) { return 2; } }\n"
'printf("%d\\n", calls);\nint_array_free(a);')
out = self.run_clean(source, [SOURCE / "int_array.c"], extra=WRAP, context="10000 int_array_push")
calls = int(out.strip())
if calls > 20:
self.fail(f"10000 int_array_push hicieron {calls} reservas. "
"Si la capacidad se duplica, bastan unas 15.")
def test_push_without_memory_keeps_the_data(self):
source = FAILING_ALLOCATOR + array_main(
"for (fail_at = 1; ; fail_at++) {\n"
" calls = 0;\n"
" struct int_array *a = int_array_new();\n"
" int pushed = 0;\n"
" if (a != NULL) {\n"
" for (int i = 0; i < 100; i++) {\n"
" size_t before = a->count;\n"
" if (int_array_push(a, i)) { pushed++; }\n"
' else if (a->count != before) { printf("cambió count\\n"); return 3; }\n'
" }\n"
" for (int i = 0; i < pushed; i++) {\n"
' if (a->items[i] < 0) { printf("dato perdido\\n"); return 3; }\n'
" }\n"
" }\n"
" int_array_free(a);\n"
" if (calls < fail_at) { break; }\n"
"}\n"
'printf("listo\\n");')
out = self.run_clean(source, [SOURCE / "int_array.c"], extra=WRAP,
context="int_array_push cuando realloc devuelve NULL")
self.assertIn("listo", out)
def test_read_small_file(self):
self.same(self.read("12\n-5\n40\n", "tres líneas"), "3 12 -5 40", "archivo de tres líneas")
def test_read_last_line_without_newline(self):
self.same(self.read("1\n2", "sin salto al final"), "2 1 2", "archivo sin salto de línea al final")
def test_read_empty_file(self):
self.same(self.read("", "archivo vacío"), "0", "un archivo vacío da un arreglo vacío")
def test_read_long_file(self):
values = [i * 2 - 50000 for i in range(50000)]
content = "".join(f"{v}\n" for v in values)
expected = " ".join(map(str, [len(values), *values]))
if self.read(content, "50000 líneas") != expected:
self.fail("El archivo de 50000 líneas no se leyó bien.")
def test_read_bad_files_release_everything(self):
cases = {
"una letra": "1\nx\n3\n",
"dos números en una línea": "1\n2 3\n",
"un número y algo más": "4 hola\n",
"una línea vacía": "1\n\n2\n",
"muchas líneas y luego una mala": "1\n" * 500 + "mal\n",
}
for label, content in cases.items():
with self.subTest(label):
self.same(self.read(content, label), "NULL", f"{label}: debe devolver NULL")
def test_read_closes_the_file(self):
body = ("for (int i = 0; i < 100; i++) {\n"
' struct int_array *good = int_array_read_file("good.txt");\n'
' if (good == NULL) { printf("falló la lectura %d\\n", i); return 1; }\n'
" int_array_free(good);\n"
' int_array_free(int_array_read_file("bad.txt"));\n'
"}")
self.run_clean(array_main(body), [SOURCE / "int_array.c"],
{"good.txt": "1\n2\n", "bad.txt": "1\nmal\n"},
context="100 lecturas seguidas: ¿se cierra el archivo en todos los caminos?",
setup=limit_open_files)
def test_read_missing_file(self):
body = 'printf("%s\\n", int_array_read_file("no_existe.txt") == NULL ? "NULL" : "otra");'
out = self.run_clean(array_main(body), [SOURCE / "int_array.c"], context="archivo que no existe")
self.same(out.strip(), "NULL", "un archivo que no existe debe dar NULL")
def test_read_without_memory_releases_everything(self):
source = FAILING_ALLOCATOR + array_main(
"int nulls = 0;\n"
"for (fail_at = 1; ; fail_at++) {\n"
" calls = 0;\n"
' struct int_array *a = int_array_read_file("in.txt");\n'
" if (a == NULL) { nulls++; }\n"
" int_array_free(a);\n"
" if (calls < fail_at) { break; }\n"
"}\n"
'printf("%d\\n", nulls);')
content = "".join(f"{i}\n" for i in range(100))
out = self.run_clean(source, [SOURCE / "int_array.c"], {"in.txt": content}, WRAP,
context="int_array_read_file cuando no hay memoria")
if int(out.strip().splitlines()[-1]) < 1:
self.fail("int_array_read_file debe devolver NULL cuando no hay memoria.")
def test_sum_file(self):
source = (HERE / "lab" / "sum_file.c").read_text(encoding="utf-8")
out, code = compile_and_run(source, [SOURCE / "int_array.c"], {"in.txt": "5\n-2\n10\n"},
stdin="in.txt\n")
if code != 0:
self.fail(f"sum_file falló:\n{short_report(out)}")
self.same(out.strip(), "3 enteros, suma 13", "lo que escribe sum_file")
class YourTests(LabTest):
"""Comprueba que sus archivos de prueba cubren lo pedido y que pasan."""
CASES = {
"image_tests.c": (["image_new", "image_free", "image_load"], ["image.c"]),
"str_tests.c": (["str_duplicate", "str_concat"], ["str_tools.c"]),
"array_tests.c": (["int_array_push", "int_array_read_file"], ["int_array.c"]),
}
def check(self, file_name):
path = SOURCE / file_name
if not path.exists():
self.skipTest(f"no está {LAB_DIR}/{file_name}")
names, others = self.CASES[file_name]
text = re.sub(r"//[^\n]*", "", path.read_text(encoding="utf-8"))
for name in names:
self.assertRegex(text, rf"\b{name}\(", f"{file_name} no prueba {name}")
with tempfile.TemporaryDirectory() as folder:
binary = pathlib.Path(folder) / "tests"
built = subprocess.run(
["gcc", *FLAGS, f"-I{HEADERS}", str(path), *[str(SOURCE / o) for o in others],
"-o", str(binary)],
text=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
)
if built.returncode != 0:
self.fail(f"{file_name} no compila:\n{built.stdout}")
done = subprocess.run(
[str(binary)], cwd=HERE, text=True, encoding="utf-8",
stdout=subprocess.PIPE, stderr=subprocess.STDOUT, timeout=60,
)
if done.returncode != 0:
self.fail(f"{file_name} falla:\n{short_report(done.stdout)}")
def test_image_tests(self):
self.check("image_tests.c")
def test_str_tests(self):
self.check("str_tests.c")
def test_array_tests(self):
self.check("array_tests.c")
if __name__ == "__main__":
unittest.main()"""Vuelve a correr los comandos que aparecen en los apuntes y en las diapositivas
y comprueba que el código citado existe de verdad en esta carpeta."""
import os
import pathlib
import platform
import re
import subprocess
import unittest
try:
import pty
except ImportError:
pty = None
HERE = pathlib.Path(__file__).parent
ROOT = HERE.parent.parent
NOTES = ROOT / "notes" / "week-05.md"
SLIDES = ROOT / "slides" / "week-05.tex"
# Antes de un bloque cuya salida cambia con el sistema. No se compara.
SYSTEM_DEPENDENT = "<!-- depende del sistema -->"
# Antes de un informe de ASan o de valgrind. Puede omitir líneas, pero no cambiarlas ni
# reordenarlas. Las direcciones y los números de proceso no se comparan.
REPORT = "<!-- informe recortado -->"
# Antes de una parte de lo que escribe image_demo con la solución.
DEMO_OUTPUT = "<!-- salida de image_demo -->"
# Los informes se capturaron en esta arquitectura: en otra cambian líneas de las bibliotecas.
REPORT_MACHINE = "aarch64"
def normalize(text):
# Según el idioma del entorno, gcc usa comillas tipográficas o rectas.
return text.replace("‘", "'").replace("’", "'")
def run_shell(command):
done = subprocess.run(
["bash", "-c", command], cwd=HERE, text=True, encoding="utf-8",
stdin=subprocess.DEVNULL, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, timeout=60,
)
return normalize(done.stdout)
def run_in_terminal(command):
"""Corre el comando en una terminal de verdad: así se intercala la salida del programa
con el informe, igual que cuando ustedes lo corren."""
master, slave = pty.openpty()
process = subprocess.Popen(
["bash", "-c", command], cwd=HERE, stdin=subprocess.DEVNULL, stdout=slave, stderr=slave,
)
os.close(slave)
data = b""
while True:
try:
chunk = os.read(master, 4096)
except OSError:
break
if not chunk:
break
data += chunk
os.close(master)
process.wait(timeout=60)
text = data.decode("utf-8", errors="replace").replace("\r", "")
return normalize(re.sub(r"\x1b\[[0-9;]*[mK]", "", text))
def clean_lines(text):
return [line.rstrip() for line in text.splitlines()]
def stable(line):
"""Quita de una línea de informe lo que cambia en cada corrida."""
line = re.sub(r"0x[0-9a-f]+", "ADDR", line.strip())
return re.sub(r"==\d+==", "==N==", line)
def split_commands(lines):
"""Parte un bloque de terminal en pares (comando, líneas de salida)."""
pairs = []
for line in lines:
if line.startswith("$ "):
pairs.append((line[2:], []))
elif pairs:
pairs[-1][1].append(normalize(line.rstrip()))
return pairs
def note_blocks(language):
"""Devuelve (líneas, marca) de cada bloque de los apuntes."""
blocks, current, mark, previous = [], None, "", ""
for raw in NOTES.read_text(encoding="utf-8").splitlines():
line = re.sub(r"^> ?", "", raw)
if current is None:
if line.strip() == "```" + language:
current, mark = [], previous.strip()
elif line.strip():
previous = line
elif line.strip() == "```":
blocks.append((current, mark))
current = None
else:
current.append(line)
return blocks
def slide_blocks(environments):
text = SLIDES.read_text(encoding="utf-8")
pattern = r"\\begin\{(?:%s)\}[^\n]*\n(.*?)\\end\{(?:%s)\}" % (environments, environments)
return [match.group(1).splitlines() for match in re.finditer(pattern, text, re.S)]
def known_code_lines():
lines = set()
sources = list(HERE.glob("*.c")) + list((HERE / "errors").glob("*.c"))
sources += list((HERE / "lab").glob("*.c")) + list((HERE / "lab").glob("*.h"))
sources += list((HERE / "lab" / "hunt").glob("*.c"))
for source in sources:
lines.update(line.strip() for line in source.read_text(encoding="utf-8").splitlines())
return lines
class ReportCheck(unittest.TestCase):
def assert_in_order(self, command, expected, actual):
"""Las líneas esperadas aparecen en la salida real, en orden. '...' marca un recorte."""
wanted = [stable(line) for line in expected if line.strip() and line.strip() != "..."]
real = [stable(line) for line in actual]
position = 0
for line in wanted:
with self.subTest(command=command, line=line):
found = [i for i in range(position, len(real)) if real[i] == line]
self.assertTrue(found, f"no aparece en la salida real: {line}")
if found:
position = found[0] + 1
class NotesAreTrue(ReportCheck):
@unittest.skipUnless(NOTES.exists(), "faltan los apuntes")
def test_terminal_blocks_in_notes(self):
checked = 0
for lines, mark in note_blocks("text"):
if not lines or not lines[0].startswith("$ ") or mark == REPORT:
continue
for command, expected in split_commands(lines):
actual = clean_lines(run_shell(command))
checked += 1
if mark != SYSTEM_DEPENDENT:
with self.subTest(command=command):
self.assertEqual(actual, expected)
self.assertGreater(checked, 3)
@unittest.skipUnless(NOTES.exists(), "faltan los apuntes")
@unittest.skipUnless(pty and platform.machine() == REPORT_MACHINE, "los informes son de aarch64")
def test_reports_in_notes(self):
checked = 0
for lines, mark in note_blocks("text"):
if mark != REPORT:
continue
for command, expected in split_commands(lines):
actual = clean_lines(run_in_terminal(command))
self.assert_in_order(command, expected, actual)
checked += 1
self.assertGreater(checked, 10)
@unittest.skipUnless(NOTES.exists(), "faltan los apuntes")
def test_code_blocks_in_notes_exist(self):
known = known_code_lines()
for lines, _ in note_blocks("c"):
for line in lines:
if line.strip() and line.strip() != "...":
with self.subTest(line=line):
self.assertIn(line.strip(), known)
@unittest.skipUnless(NOTES.exists() and (HERE / "solution").exists(), "falta la solución")
def test_demo_output_in_notes(self):
actual = clean_lines(run_shell("build/solution/image_demo"))
found = 0
for lines, mark in note_blocks("text"):
if mark == DEMO_OUTPUT:
text = "\n".join(line.rstrip() for line in lines)
self.assertIn(text, "\n".join(actual))
found += 1
self.assertGreater(found, 0)
class SlidesAreTrue(ReportCheck):
@unittest.skipUnless(SLIDES.exists(), "faltan las diapositivas")
def test_program_output_in_slides(self):
for block in slide_blocks("terminal|salida"):
for command, expected in split_commands(block):
self.assert_in_order(command, expected, clean_lines(run_shell(command)))
# Algunos informes usan programas que compiló la prueba de los apuntes, que corre antes.
@unittest.skipUnless(SLIDES.exists(), "faltan las diapositivas")
@unittest.skipUnless(pty and platform.machine() == REPORT_MACHINE, "los informes son de aarch64")
def test_reports_in_slides(self):
for block in slide_blocks("informe"):
for command, expected in split_commands(block):
self.assert_in_order(command, expected, clean_lines(run_in_terminal(command)))
@unittest.skipUnless(SLIDES.exists(), "faltan las diapositivas")
def test_code_blocks_in_slides_exist(self):
known = known_code_lines()
for block in slide_blocks("codigo|codigochico|codigomini"):
for line in block:
if line.strip() and line.strip() != "...":
with self.subTest(line=line):
self.assertIn(line.strip(), known)
if __name__ == "__main__":
unittest.main()Laboratorio
Archivos de partida del laboratorio de la semana.
// Sesión 2: pruebas del arreglo que crece.
// Agreguen sus casos: un archivo vacío, una línea mal formada, un archivo que no existe.
#include <assert.h>
#include <stdio.h>
#include "int_array.h"
static void test_push_keeps_the_order(void) {
struct int_array *array = int_array_new();
assert(array != NULL);
for (int i = 0; i < 100; i++) {
assert(int_array_push(array, i * i));
}
assert(array->count == 100);
assert(array->capacity >= 100);
for (int i = 0; i < 100; i++) {
assert(array->items[i] == i * i);
}
int_array_free(array);
}
int main(void) {
test_push_keeps_the_order();
printf("Todas las pruebas pasaron\n");
return 0;
}// Los filtros de la semana 4, con los píxeles en un solo bloque, fila por fila.
#include "filters.h"
void invert(struct image *img) {
int total = img->width * img->height;
for (int i = 0; i < total; i++) {
img->pixels[i] = img->max_value - img->pixels[i];
}
}
void threshold(struct image *img, int level) {
int total = img->width * img->height;
for (int i = 0; i < total; i++) {
img->pixels[i] = img->pixels[i] >= level ? img->max_value : 0;
}
}
void flip_horizontal(struct image *img) {
for (int y = 0; y < img->height; y++) {
int *row = img->pixels + y * img->width;
for (int x = 0; x < img->width / 2; x++) {
int other = img->width - 1 - x;
int saved = row[x];
row[x] = row[other];
row[other] = saved;
}
}
}
void image_stats(const struct image *img, int *min, int *max, double *mean) {
int total = img->width * img->height;
long sum = 0;
*min = img->pixels[0];
*max = img->pixels[0];
for (int i = 0; i < total; i++) {
int value = img->pixels[i];
if (value < *min) {
*min = value;
}
if (value > *max) {
*max = value;
}
sum = sum + value;
}
*mean = (double) sum / total;
}// Filtros: cambian la imagen en su lugar. Ya están hechos, y ninguno reserva memoria.
#ifndef FILTERS_H
#define FILTERS_H
#include "image.h"
// Cada píxel p pasa a max_value - p.
void invert(struct image *img);
// Cada píxel mayor o igual que level pasa a max_value. Los demás, a 0.
void threshold(struct image *img, int level);
// Espejo izquierda-derecha: cada fila queda al revés.
void flip_horizontal(struct image *img);
// Calcula el mínimo, el máximo y el promedio de los píxeles.
void image_stats(const struct image *img, int *min, int *max, double *mean);
#endif// Carga una imagen más grande que el límite de la semana 3, la dibuja y guarda un resultado.
#include <stdio.h>
#include "filters.h"
int main(void) {
struct image *img = image_load("lab/images/range.pgm");
if (img == NULL) {
fprintf(stderr, "No se pudo leer lab/images/range.pgm\n");
return 1;
}
int min;
int max;
double mean;
image_stats(img, &min, &max, &mean);
printf("%d x %d, de %d a %d, promedio %.1f\n", img->width, img->height, min, max, mean);
image_show(img);
flip_horizontal(img);
int saved = image_save(img, "build/range_flip.pgm");
if (!saved) {
fprintf(stderr, "No se pudo escribir build/range_flip.pgm\n");
}
image_free(img);
return saved ? 0 : 1;
}// Sesión 1: pruebas del módulo de imágenes.
// Agreguen sus casos: image_new, image_free con NULL, y lo que debe fallar.
#include <assert.h>
#include <stdio.h>
#include "image.h"
static void test_load_tiny(void) {
const int expected[] = {0, 10, 20,
30, 40, 50};
struct image *wanted = image_from_values(3, 2, 255, expected);
struct image *img = image_load("lab/images/tiny.pgm");
assert(wanted != NULL);
assert(img != NULL);
assert(image_equal(img, wanted));
image_free(img);
image_free(wanted);
}
int main(void) {
test_load_tiny();
printf("Todas las pruebas pasaron\n");
return 0;
}// Implementación del módulo de imágenes. Ver image.h.
// Sesión 1: escriban image_allocate, image_new e image_free, y terminen read_image.
#include "image.h"
#include <ctype.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
// En cada línea del archivo: el formato pide no pasar de 70 caracteres.
#define VALUES_PER_LINE 16
// La división evita calcular width * height, que podría no caber en un int.
static int valid_size(int width, int height, int max_value) {
return width >= 1 && height >= 1 && width <= INT_MAX / height
&& max_value >= 1 && max_value <= IMAGE_MAX_VALUE;
}
// Devuelve una imagen nueva con sus píxeles sin inicializar, o NULL si los datos no son
// válidos o si no hay memoria. Son dos bloques: el struct y los píxeles. Si el segundo
// malloc falla, no olviden liberar el primero.
// Si el segundo malloc falla, no olviden liberar el primero.
static struct image *image_allocate(int width, int height, int max_value) {
if (!valid_size(width, height, max_value)) {
return NULL;
}
// Escriban aquí su código.
return NULL;
}
struct image *image_new(int width, int height, int max_value, int value) {
(void) width; // borren estas cuatro líneas cuando usen los parámetros
(void) height;
(void) max_value;
(void) value;
// Escriban aquí su código. Usen image_allocate.
return NULL;
}
// Ya está hecha, y sirve de modelo: si un valor está mal, libera lo que reservó.
struct image *image_from_values(int width, int height, int max_value, const int *values) {
struct image *img = image_allocate(width, height, max_value);
if (img == NULL) {
return NULL;
}
int total = width * height;
for (int i = 0; i < total; i++) {
if (values[i] < 0 || values[i] > max_value) {
image_free(img);
return NULL;
}
img->pixels[i] = values[i];
}
return img;
}
void image_free(struct image *img) {
(void) img; // borren esta línea cuando usen img
// Escriban aquí su código.
}
int image_equal(const struct image *a, const struct image *b) {
if (a->width != b->width || a->height != b->height || a->max_value != b->max_value) {
return 0;
}
int total = a->width * a->height;
for (int i = 0; i < total; i++) {
if (a->pixels[i] != b->pixels[i]) {
return 0;
}
}
return 1;
}
// Devuelve el siguiente número del archivo, o -1 si lo que sigue no es un número o no
// cabe en un int. Se salta los espacios y los comentarios, que van de # hasta el fin de
// la línea.
static int read_number(FILE *file) {
int c = fgetc(file);
while (c == '#' || isspace(c)) {
if (c == '#') {
while (c != '\n' && c != EOF) {
c = fgetc(file);
}
} else {
c = fgetc(file);
}
}
if (!isdigit(c)) {
return -1;
}
int value = 0;
while (isdigit(c)) {
int digit = c - '0';
if (value > (INT_MAX - digit) / 10) {
return -1;
}
value = value * 10 + digit;
c = fgetc(file);
}
// Lo que no era parte del número le toca a la siguiente lectura.
if (c != EOF) {
ungetc(c, file);
}
return value;
}
// Lee la imagen de un archivo ya abierto. Lo que venga después del último píxel se ignora.
static struct image *read_image(FILE *file) {
if (fgetc(file) != 'P' || fgetc(file) != '2') {
return NULL;
}
int width = read_number(file);
int height = read_number(file);
int max_value = read_number(file);
(void) width; // borren estas tres líneas cuando usen las variables
(void) height;
(void) max_value;
// Escriban aquí su código. Reserven con image_allocate y lean píxel por píxel, con
// read_number. Un píxel fuera de rango, o que falta, hace fallar toda la lectura.
return NULL;
}
struct image *image_load(const char *name) {
FILE *file = fopen(name, "r");
if (file == NULL) {
return NULL;
}
struct image *img = read_image(file);
fclose(file);
return img;
}
int image_save(const struct image *img, const char *name) {
FILE *file = fopen(name, "w");
if (file == NULL) {
return 0;
}
int written = fprintf(file, "P2\n%d %d\n%d\n", img->width, img->height, img->max_value);
for (int y = 0; y < img->height && written >= 0; y++) {
for (int x = 0; x < img->width && written >= 0; x++) {
int last_in_line = x == img->width - 1 || x % VALUES_PER_LINE == VALUES_PER_LINE - 1;
written = fprintf(file, "%d%c", img->pixels[y * img->width + x],
last_in_line ? '\n' : ' ');
}
}
// Al cerrar se escribe lo que quedaba pendiente, y eso también puede fallar.
if (fclose(file) != 0 || written < 0) {
return 0;
}
return 1;
}
void image_print(const struct image *img) {
printf("%d x %d, max %d\n", img->width, img->height, img->max_value);
for (int y = 0; y < img->height; y++) {
for (int x = 0; x < img->width; x++) {
printf("%4d", img->pixels[y * img->width + x]);
}
printf("\n");
}
}
void image_show(const struct image *img) {
const char shades[] = " .:-=+*#%@";
int last_shade = (int) sizeof(shades) - 2;
for (int y = 0; y < img->height; y++) {
for (int x = 0; x < img->width; x++) {
putchar(shades[img->pixels[y * img->width + x] * last_shade / img->max_value]);
}
putchar('\n');
}
}// Módulo de imágenes en escala de grises, en formato PGM de texto (P2).
// Ahora sin tamaño máximo: cada imagen ocupa dos bloques del heap.
#ifndef IMAGE_H
#define IMAGE_H
#define IMAGE_MAX_VALUE 255
// pixels apunta a width * height enteros, fila por fila: el píxel de la fila y y la
// columna x es pixels[y * width + x]. Cada uno vale de 0 (negro) a max_value (blanco).
struct image {
int width;
int height;
int max_value;
int *pixels;
};
// Las tres funciones siguientes devuelven una imagen nueva. Quien llama es su owner y
// debe liberarla con image_free. Si algo falla, devuelven NULL y no queda nada reservado.
// Imagen con todos sus píxeles en value. Falla si algún dato no está en su rango, si
// width * height no cabe en un int o si no hay memoria.
struct image *image_new(int width, int height, int max_value, int value);
// Igual, pero toma los píxeles de values, fila por fila (width * height elementos).
struct image *image_from_values(int width, int height, int max_value, const int *values);
// Lee un archivo PGM de texto. Falla si no se puede abrir, si no es P2, si sus datos
// están mal o si no hay memoria.
struct image *image_load(const char *name);
// Libera la imagen y sus píxeles. Con NULL no hace nada.
void image_free(struct image *img);
// Devuelve 1 si tienen el mismo tamaño, el mismo max_value y los mismos píxeles.
int image_equal(const struct image *a, const struct image *b);
// Escribe la imagen en un archivo PGM de texto. Devuelve 1 si pudo y 0 si no.
int image_save(const struct image *img, const char *name);
// Escribe el tamaño y los números de cada píxel, para imágenes pequeñas.
void image_print(const struct image *img);
// Dibuja la imagen en la terminal con letras: más claro, más lleno.
void image_show(const struct image *img);
#endif// Sesión 2: escriban el arreglo que crece. Lo que pide cada función está en int_array.h.
#include "int_array.h"
#include <stdio.h>
#include <stdlib.h>
// Cada línea del archivo cabe de sobra aquí: solo trae un entero.
#define LINE_SIZE 64
struct int_array *int_array_new(void) {
// Escriban aquí su código.
return NULL;
}
int int_array_push(struct int_array *array, int value) {
(void) array; // borren estas dos líneas cuando usen los parámetros
(void) value;
// Escriban aquí su código.
return 0;
}
void int_array_free(struct int_array *array) {
(void) array; // borren esta línea cuando usen array
// Escriban aquí su código.
}
struct int_array *int_array_read_file(const char *name) {
(void) name; // borren esta línea cuando usen name
// Escriban aquí su código. Lean con fgets y LINE_SIZE, y separen con sscanf, como en la
// semana 2. Cierren el archivo y liberen el arreglo en todos los caminos de error.
return NULL;
}// Un arreglo de enteros que crece con realloc.
#ifndef INT_ARRAY_H
#define INT_ARRAY_H
#include <stddef.h>
// items apunta a capacity enteros, de los que solo count tienen datos.
// Si capacity es 0, items puede ser NULL.
struct int_array {
int *items;
size_t count;
size_t capacity;
};
// Devuelve un arreglo nuevo y vacío, o NULL si no hay memoria.
// Quien llama es su owner, y debe liberarlo con int_array_free.
struct int_array *int_array_new(void);
// Agrega value al final, y devuelve 1 si pudo. Cuando no hay lugar, la capacidad se duplica,
// salvo si era 0: entonces pasa a 4.
// Si no hay memoria, devuelve 0 y el arreglo queda como estaba, con todos sus datos.
int int_array_push(struct int_array *array, int value);
// Libera el arreglo y sus elementos. Con NULL no hace nada.
void int_array_free(struct int_array *array);
// Lee un archivo de texto con un entero por línea, de cualquier largo. Un archivo vacío da
// un arreglo vacío. Devuelve NULL si no se puede abrir, si una línea no tiene exactamente un
// entero o si no hay memoria. Quien llama es owner del resultado.
struct int_array *int_array_read_file(const char *name);
#endif# Laboratorio de la semana 5: imágenes de cualquier tamaño y cacería de errores
Los comandos se corren desde `code/week-05`. Las ideas están en los apuntes, en la sección
"El laboratorio, paso a paso".
| Archivo | Qué es | ¿Lo cambian? |
|---|---|---|
| `image.h` | Lo que ofrece el módulo de imágenes, ahora sin tamaño máximo | No |
| `image.c` | El módulo: `read_number`, guardar, comparar y dibujar ya están hechos | **Sí** |
| `filters.h`, `filters.c` | Los filtros de la semana 4, ya adaptados | No |
| `image_tests.c` | Sus pruebas del módulo | **Sí** |
| `image_demo.c` | Carga `images/range.pgm`, de 96 x 28, y la dibuja | No |
| `str_tools.h` | Lo que deben hacer `str_duplicate` y `str_concat` | No |
| `str_tools.c`, `str_tests.c` | Las dos funciones y sus pruebas | **Sí** |
| `hunt/*.c` | Los cinco programas de la cacería | **Sí** |
| `hunt/report.md` | Su explicación de cada error | **Sí** |
| `int_array.h` | Lo que debe hacer el arreglo que crece | No |
| `int_array.c`, `array_tests.c` | El arreglo y sus pruebas | **Sí** |
| `sum_file.c` | Usa el arreglo para resumir un archivo de enteros | No |
```sh
make lab # compila sus programas y sus pruebas en build/lab/
make hunt # compila la cacería en build/hunt/, con sanitizers y para valgrind
make test-lab # comprueba todo; falla hasta que terminen
```
Cada sesión está pensada para unos 90 minutos de trabajo, y el resto queda para preguntar.
## Sesión 1
### Parte 1: leer antes de cambiar (10 minutos)
Lean `image.h` completo y luego `image_from_values`, `read_number` e `image_show` en
`image.c`. Respondan por escrito, sin ejecutar nada.
1. Según `image.h`, ¿quién es owner de lo que devuelve `image_load`? ¿Cómo debe liberarlo?
2. En una imagen de 3 x 2, ¿cuántos bloques hay en el heap? ¿Cuántos bytes pide cada uno,
según `sizeof`?
3. En `image_from_values`, ¿qué pasaría con la memoria si, al encontrar un valor malo, se
hiciera solo `return NULL`?
4. ¿Por qué `valid_size` compara `width <= INT_MAX / height`, en lugar de calcular
`width * height` y comparar el resultado?
### Parte 2: el módulo sin límite (50 minutos)
En `image.c`, escriban `image_allocate`, `image_new` e `image_free`, y terminen `read_image`.
Agreguen sus casos en `image_tests.c`, que trae uno de modelo.
```sh
make lab && build/lab/image_tests
build/lab/image_demo
```
`read_image` no debe llamar a `image_new`: esa función escribe todos los píxeles antes de
leer el primero. Si el archivo dice que la imagen es enorme y trae pocos datos, ese trabajo
se desperdicia.
### Parte 3: cadenas que devuelven memoria nueva (30 minutos)
En `str_tools.c`, escriban `str_duplicate` y `str_concat`, según `str_tools.h`, con sus
pruebas en `str_tests.c`. No usen `strdup`: no es parte de C17. Prueben la cadena vacía.
```sh
make lab && build/lab/str_tests
```
Si no alcanza el tiempo, `str_concat` y sus pruebas quedan para antes de la sesión 2.
## Sesión 2
### Parte 4: la cacería (50 minutos, unos 10 por programa)
Cada programa de `hunt/` tiene un solo tipo de error, distinto al de los otros, y todos
compilan sin advertencias. Para cada uno, en este orden:
1. Córranlo con `build/hunt/nombre`, y con `valgrind --leak-check=full build/hunt/nombre_vg`.
2. Lean el informe y **dibujen** el bloque y los punteros en el momento del error.
3. Escriban la explicación en `hunt/report.md`.
4. Corrijan el programa. La salida correcta debe ser la que el programa quería dar: en
`route_backup.c`, por ejemplo, el respaldo debe conservar el nombre original.
Un programa puede parecer correcto con una herramienta y no con la otra. Revisen las dos
antes de decidir que no tiene errores.
### Parte 5: un arreglo que crece (40 minutos)
En `int_array.c`, escriban `int_array_new`, `int_array_push` e `int_array_free`, según
`int_array.h`, y después `int_array_read_file`. Agreguen sus casos en `array_tests.c`.
```sh
make lab && build/lab/array_tests
echo lab/data/numbers.txt | build/lab/sum_file
```
Cuando pasen, corran `make test-lab`. Las pruebas comprueban los programas corregidos, pero
no leen `hunt/report.md`.
Si no alcanza el tiempo, `int_array_read_file` queda para antes de la próxima clase.// Sesión 1: pruebas de str_duplicate y str_concat.
// Agreguen sus casos. Piensen en la cadena vacía. Toda cadena que reciban la liberan.
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "str_tools.h"
static void test_duplicate_is_a_new_block(void) {
const char *text = "Lima";
char *copy = str_duplicate(text);
assert(copy != NULL);
assert(strcmp(copy, "Lima") == 0);
assert(copy != text);
free(copy);
}
int main(void) {
test_duplicate_is_a_new_block();
printf("Todas las pruebas pasaron\n");
return 0;
}// Sesión 1: escriban str_duplicate y str_concat. Lo que pide cada una está en str_tools.h.
#include "str_tools.h"
#include <stdlib.h>
#include <string.h>
char *str_duplicate(const char *text) {
(void) text; // borren esta línea cuando usen text
// Escriban aquí su código.
return NULL;
}
char *str_concat(const char *first, const char *second) {
(void) first; // borren estas dos líneas cuando usen los parámetros
(void) second;
// Escriban aquí su código.
return NULL;
}// Cadenas que devuelven memoria nueva.
#ifndef STR_TOOLS_H
#define STR_TOOLS_H
// Devuelve una copia de text en un bloque nuevo, o NULL si no hay memoria.
// Quien llama es owner del resultado, y debe liberarlo con free.
char *str_duplicate(const char *text);
// Devuelve first seguida de second en un bloque nuevo, o NULL si no hay memoria.
// No cambia ninguna de las dos. Quien llama es owner del resultado, y debe liberarlo con free.
char *str_concat(const char *first, const char *second);
#endif// Lee el nombre de un archivo de la entrada, y resume los enteros que tiene.
#include <stdio.h>
#include "int_array.h"
#define PATH_SIZE 256
// Quita el salto de línea que deja fgets, si lo hay.
static void remove_newline(char *text) {
for (int i = 0; text[i] != '\0'; i++) {
if (text[i] == '\n') {
text[i] = '\0';
return;
}
}
}
int main(void) {
char path[PATH_SIZE];
if (fgets(path, PATH_SIZE, stdin) == NULL) {
fprintf(stderr, "Falta el nombre del archivo\n");
return 1;
}
remove_newline(path);
struct int_array *array = int_array_read_file(path);
if (array == NULL) {
fprintf(stderr, "No se pudo leer %s\n", path);
return 1;
}
long total = 0;
for (size_t i = 0; i < array->count; i++) {
total = total + array->items[i];
}
printf("%zu enteros, suma %ld\n", array->count, total);
int_array_free(array);
return 0;
}12
-5
40
7
100// Cuenta cuántas veces aparece cada nota, de 0 a 20, y escribe las que aparecen.
#include <stdio.h>
#include <stdlib.h>
#define MAX_GRADE 20
int main(void) {
const int grades[] = {14, 20, 7, 14, 20, 11};
int total = sizeof(grades) / sizeof(grades[0]);
int *counts = calloc(MAX_GRADE, sizeof *counts);
if (counts == NULL) {
return 1;
}
for (int i = 0; i < total; i++) {
counts[grades[i]]++;
}
for (int grade = 0; grade <= MAX_GRADE; grade++) {
if (counts[grade] > 0) {
printf("%d: %d\n", grade, counts[grade]);
}
}
free(counts);
return 0;
}// Escribe en mayúsculas la palabra más larga de la lista.
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
static char *upper_word(const char *word) {
char *copy = malloc(strlen(word) + 1);
if (copy == NULL) {
return NULL;
}
strcpy(copy, word);
for (size_t i = 0; copy[i] != '\0'; i++) {
copy[i] = (char) toupper((unsigned char) copy[i]);
}
return copy;
}
int main(void) {
const char *words[] = {"puno", "ayacucho", "ica", "cajamarca", "lima"};
char *longest = NULL;
for (int i = 0; i < 5; i++) {
char *upper = upper_word(words[i]);
if (upper == NULL) {
free(longest);
return 1;
}
if (longest == NULL || strlen(upper) > strlen(longest)) {
longest = upper;
}
}
printf("%s\n", longest);
free(longest);
return 0;
}// Suma los puntos de cada grupo y cuenta cuántos grupos llegan a 30.
#include <stdio.h>
#include <stdlib.h>
#define GROUPS 4
int main(void) {
const int groups[] = {0, 1, 2, 3, 0, 1, 2, 0, 1, 0};
const int points[] = {12, 5, 9, 2, 15, 8, 3, 6, 4, 1};
int total = sizeof(groups) / sizeof(groups[0]);
int *sums = malloc(GROUPS * sizeof *sums);
if (sums == NULL) {
return 1;
}
for (int i = 0; i < total; i++) {
sums[groups[i]] = sums[groups[i]] + points[i];
}
int passing = 0;
for (int g = 0; g < GROUPS; g++) {
if (sums[g] >= 30) {
passing++;
}
}
printf("grupos que llegan a 30: %d\n", passing);
free(sums);
return 0;
}# Cacería de errores: informe
Por cada programa, antes de corregirlo, escriban:
- **Herramienta:** con cuál lo encontraron (ASan o valgrind) y qué línea del informe dice el tipo de error.
- **Qué pasó:** con sus palabras y mirando el modelo: qué bloque, quién lo reservó, y qué se hizo mal con él.
- **Línea culpable:** la del error, que puede no ser la que el informe marca primero.
- **Corrección:** qué cambiaron, y por qué esa es la solución y no otra.
## `longest_word.c`
## `seat_list.c`
## `route_backup.c`
## `grade_histogram.c`
## `passing_groups.c`// Guarda un respaldo de una ruta antes de cambiarle el nombre.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct route {
char *name;
int stops;
};
int main(void) {
struct route route;
route.stops = 12;
route.name = malloc(strlen("Callao") + 1);
if (route.name == NULL) {
return 1;
}
strcpy(route.name, "Callao");
struct route backup = route;
route.name[0] = 'K';
printf("ruta: %s, respaldo: %s\n", route.name, backup.name);
free(route.name);
free(backup.name);
return 0;
}// Agranda una lista de asientos, y escribe el primero y el último.
#include <stdio.h>
#include <stdlib.h>
struct seat_list {
int *seats;
int count;
};
// Cambia la lista por otra del doble de largo, con los mismos asientos al inicio.
static int double_list(struct seat_list *list) {
int *bigger = malloc(2 * list->count * sizeof *bigger);
if (bigger == NULL) {
return 0;
}
for (int i = 0; i < list->count; i++) {
bigger[i] = list->seats[i];
bigger[list->count + i] = list->seats[i] + 100;
}
free(list->seats);
list->seats = bigger;
list->count = 2 * list->count;
return 1;
}
int main(void) {
struct seat_list list;
list.count = 3;
list.seats = malloc(list.count * sizeof *list.seats);
if (list.seats == NULL) {
return 1;
}
for (int i = 0; i < list.count; i++) {
list.seats[i] = i + 1;
}
int *first = list.seats;
if (!double_list(&list)) {
free(list.seats);
return 1;
}
printf("primero: %d, último: %d\n", *first, list.seats[list.count - 1]);
free(list.seats);
return 0;
}P2
# Una cordillera y un sol: más ancha que el límite de 64 de la semana 3.
96 28
255
20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20
20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20
20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20
20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20
20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20
20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20
22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22
22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22
22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22
22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22
22 22 22 22 170 170 170 170 170 170 170 170 170 22 22 22
22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22
24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24
24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24
24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24
24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24
170 170 170 170 170 170 170 170 170 170 170 170 170 170 170 170
170 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24
26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26
26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26
26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26
26 26 26 26 26 26 26 26 26 26 26 26 26 26 170 170
170 170 170 170 250 250 250 250 250 250 250 250 250 170 170 170
170 170 170 26 26 26 26 26 26 26 26 26 26 26 26 26
28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28
28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28
28 28 28 28 28 28 28 28 28 28 28 28 28 28 28 28
28 28 28 28 28 28 28 28 28 28 28 28 28 170 170 170
170 170 250 250 250 250 250 250 250 250 250 250 250 250 250 170
170 170 170 170 28 28 28 28 28 28 28 28 28 28 28 28
30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30
30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30
30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30
30 30 30 30 30 30 30 30 30 30 30 30 170 170 170 170
250 250 250 250 250 250 250 250 250 250 250 250 250 250 250 250
250 170 170 170 170 30 30 30 30 30 30 30 30 30 30 30
32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32
32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32
32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32
32 32 32 32 32 32 32 32 32 32 32 32 170 170 170 170
250 250 250 250 250 250 250 250 250 250 250 250 250 250 250 250
250 170 170 170 170 32 32 32 32 32 32 32 32 32 32 32
34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34
34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34
34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34
34 34 34 34 34 34 34 34 34 34 34 34 170 170 170 170
250 250 250 250 250 250 250 250 250 250 250 250 250 250 250 250
250 170 170 170 170 34 34 34 34 34 34 34 34 34 34 34
36 36 36 36 36 36 36 36 36 36 36 36 36 36 36 36
36 36 36 36 36 36 36 36 36 36 36 36 36 36 36 36
36 36 36 36 36 36 36 36 36 36 36 36 36 36 36 36
36 36 36 36 36 36 36 36 36 36 36 36 170 170 170 170
250 250 250 250 250 250 250 250 250 250 250 250 250 250 250 250
250 170 170 170 170 36 36 36 36 36 36 36 36 36 36 36
38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38
38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38
38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38
38 38 38 38 38 38 38 38 38 38 38 38 170 170 170 170
250 250 250 250 250 250 250 250 250 250 250 250 250 250 250 250
250 170 170 170 170 38 38 38 38 38 38 38 38 38 38 38
40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40
40 40 40 40 40 40 235 40 40 40 40 40 40 40 40 40
40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40
40 40 40 40 40 40 40 40 40 40 40 40 40 170 170 170
170 170 250 250 250 250 250 250 250 250 250 250 250 250 250 170
170 170 170 170 40 40 40 40 40 40 40 40 40 40 40 40
42 42 42 42 42 42 42 42 42 42 42 42 42 42 42 42
42 42 42 42 42 235 235 235 42 42 42 42 42 42 42 42
42 42 42 42 42 42 42 42 42 42 42 42 42 42 42 42
42 42 42 42 42 42 42 42 42 42 42 42 42 42 170 170
170 170 170 170 250 250 250 250 250 250 250 250 250 170 170 170
170 170 170 42 42 42 42 42 42 42 42 42 42 42 42 42
44 44 44 44 44 44 44 44 44 44 44 44 44 44 44 44
44 44 44 235 235 235 235 235 235 235 44 44 44 44 44 44
44 44 44 44 44 44 44 44 44 44 44 44 44 44 44 44
44 44 44 44 44 44 44 44 44 44 44 44 44 44 44 44
170 170 170 170 170 170 170 170 170 170 170 170 170 170 170 170
170 44 44 44 44 44 44 44 44 44 44 44 44 44 44 44
46 46 46 46 46 46 46 46 46 46 46 46 46 46 46 46
46 46 129 129 129 129 129 129 129 129 129 46 46 46 46 46
46 46 46 46 46 46 46 46 46 46 46 46 46 46 46 46
46 46 46 46 46 46 46 46 46 46 46 46 46 46 46 46
46 46 46 46 170 170 170 170 170 170 170 170 170 46 46 46
46 46 46 46 46 46 46 46 46 46 46 46 46 46 46 46
48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48
127 127 127 127 127 127 127 127 127 127 127 127 127 48 48 48
48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48
48 48 48 48 235 48 48 48 48 48 48 48 48 48 48 48
48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48
48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48
50 50 50 50 50 50 50 50 50 50 50 50 50 50 125 125
125 125 125 125 125 125 125 125 125 125 125 125 125 125 125 50
50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50
50 50 50 235 235 235 50 50 50 50 50 50 50 50 50 50
50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50
50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50
52 52 52 52 52 52 52 52 52 52 52 52 52 123 123 123
123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123
52 52 52 52 52 52 52 52 52 52 52 52 52 52 52 52
52 235 235 235 235 235 235 235 52 52 52 52 52 52 52 52
52 52 52 52 52 52 52 52 52 52 52 52 52 52 52 52
52 52 52 52 52 52 52 52 52 52 52 52 52 52 52 52
54 54 54 54 54 54 54 54 54 54 54 121 121 121 121 121
121 121 121 121 121 121 121 121 121 121 121 121 121 121 121 121
121 121 54 54 54 54 54 54 54 54 54 54 54 54 54 54
129 129 129 129 129 129 129 129 129 54 54 54 54 54 54 54
54 54 54 54 54 54 54 54 54 54 54 54 54 54 54 54
54 54 54 54 54 54 54 54 54 54 54 54 54 54 54 54
56 56 56 56 56 56 56 56 56 56 119 119 119 119 119 119
119 119 119 119 119 119 119 119 119 119 119 119 119 119 119 119
119 119 119 56 56 56 56 56 56 56 56 56 56 56 127 127
127 127 127 127 127 127 127 127 127 127 127 56 56 56 56 56
56 56 56 56 56 56 56 56 56 56 56 56 56 56 56 56
56 56 56 56 56 56 56 56 56 56 56 56 56 56 56 56
58 58 58 58 58 58 58 58 117 117 117 117 117 117 117 117
117 117 117 117 117 117 117 117 117 117 117 117 117 117 117 117
117 117 117 117 117 58 58 58 58 58 58 58 125 125 125 125
125 125 125 125 125 125 125 125 125 125 125 125 125 58 58 58
58 58 58 58 58 58 58 58 58 58 58 58 58 58 58 58
58 58 58 58 235 58 58 58 58 58 58 58 58 58 58 58
60 60 60 60 60 60 115 115 115 115 115 115 115 115 115 115
115 115 115 115 115 115 115 115 115 115 115 115 115 115 115 115
115 115 115 115 115 115 115 60 60 60 60 123 123 123 123 123
123 123 123 123 123 123 123 123 123 123 123 123 123 123 60 60
60 60 60 60 60 60 60 60 60 60 60 60 60 60 60 60
60 60 60 235 235 235 60 60 60 60 60 60 60 60 60 60
62 62 62 62 62 113 113 113 113 113 113 113 113 113 113 113
113 113 113 113 113 113 113 113 113 113 113 113 113 113 113 113
113 113 113 113 113 113 113 113 62 121 121 121 121 121 121 121
121 121 121 121 121 121 121 121 121 121 121 121 121 121 121 121
62 62 62 62 62 62 62 62 62 62 62 62 62 62 62 62
62 235 235 235 235 235 235 235 62 62 62 62 62 62 62 62
64 64 64 111 111 111 111 111 111 111 111 111 111 111 111 111
111 111 111 111 111 111 111 111 111 111 111 111 111 111 111 111
111 111 111 111 111 111 111 111 119 119 119 119 119 119 119 119
119 119 119 119 119 119 119 119 119 119 119 119 119 119 119 119
119 64 64 64 64 64 64 64 64 64 64 64 64 64 64 64
129 129 129 129 129 129 129 129 129 64 64 64 64 64 64 64
66 66 109 109 109 109 109 109 109 109 109 109 109 109 109 109
109 109 109 109 109 109 109 109 109 109 109 109 109 109 109 109
109 109 109 109 109 109 117 117 117 117 117 117 117 117 117 117
117 117 117 117 117 117 117 117 117 117 117 117 117 117 117 117
117 117 117 66 66 66 66 66 66 66 66 66 66 66 127 127
127 127 127 127 127 127 127 127 127 127 127 66 66 66 66 66
107 107 107 107 107 107 107 107 107 107 107 107 107 107 107 107
107 107 107 107 107 107 107 107 107 107 107 107 107 107 107 107
107 107 107 107 115 115 115 115 115 115 115 115 115 115 115 115
115 115 115 115 115 115 115 115 115 115 115 115 115 115 115 115
115 115 115 115 115 68 68 68 68 68 68 68 125 125 125 125
125 125 125 125 125 125 125 125 125 125 125 125 125 68 68 68
105 105 105 105 105 105 105 105 105 105 105 105 105 105 105 105
105 105 105 105 105 105 105 105 105 105 105 105 105 105 105 105
105 105 105 113 113 113 113 113 113 113 113 113 113 113 113 113
113 113 113 113 113 113 113 113 113 113 113 113 113 113 113 113
113 113 113 113 113 113 70 70 70 70 70 123 123 123 123 123
123 123 123 123 123 123 123 123 123 123 123 123 123 123 70 70
103 103 103 103 103 103 103 103 103 103 103 103 103 103 103 103
103 103 103 103 103 103 103 103 103 103 103 103 103 103 103 103
103 111 111 111 111 111 111 111 111 111 111 111 111 111 111 111
111 111 111 111 111 111 111 111 111 111 111 111 111 111 111 111
111 111 111 111 111 111 111 111 72 121 121 121 121 121 121 121
121 121 121 121 121 121 121 121 121 121 121 121 121 121 121 121
101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101
101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101
109 109 109 109 109 109 109 109 109 109 109 109 109 109 109 109
109 109 109 109 109 109 109 109 109 109 109 109 109 109 109 109
109 109 109 109 109 109 109 109 119 119 119 119 119 119 119 119
119 119 119 119 119 119 119 119 119 119 119 119 119 119 119 119P2
# Un sol a la izquierda y un cerro que sube hacia la derecha.
32 16
255
40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40
40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40
40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40
40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40
40 40 40 40 40 40 40 40 255 40 40 40 40 40 40 40
40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40
40 40 40 40 255 255 255 255 255 255 255 255 255 40 40 40
40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40
40 40 40 255 255 255 255 255 255 255 255 255 255 255 40 40
40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40
40 40 255 255 255 255 255 255 255 255 255 255 255 255 255 40
40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40
40 40 40 255 255 255 255 255 255 255 255 255 255 255 40 40
40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40
40 40 40 40 255 255 255 255 255 255 255 255 255 40 40 40
40 40 40 40 40 40 40 40 40 40 40 40 40 40 130 130
40 40 40 40 40 40 40 40 255 40 40 40 40 40 40 40
40 40 40 40 40 40 40 40 40 130 130 130 130 130 130 130
40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 40
40 40 40 40 130 130 130 130 130 130 130 130 130 130 130 130
40 40 40 40 40 40 40 40 40 40 40 40 40 40 40 130
130 130 130 130 130 130 130 130 130 130 130 130 130 130 130 130
40 40 40 40 40 40 40 40 40 40 130 130 130 130 130 130
130 130 130 130 130 130 130 130 130 130 130 130 130 130 130 130
40 40 40 40 40 130 130 130 130 130 130 130 130 130 130 130
130 130 130 130 130 130 130 130 130 130 130 130 130 130 130 130
130 130 130 130 130 130 130 130 130 130 130 130 130 130 130 130
130 130 130 130 130 130 130 130 130 130 130 130 130 130 130 130
130 130 130 130 130 130 130 130 130 130 130 130 130 130 130 130
130 130 130 130 130 130 130 130 130 130 130 130 130 130 130 130
130 130 130 130 130 130 130 130 130 130 130 130 130 130 130 130
130 130 130 130 130 130 130 130 130 130 130 130 130 130 130 130P2
# Tres columnas y dos filas, con un comentario.
3 2
255
0 10 20
30 40 50Compilación
Cómo se compilan y ejecutan los ejemplos.
# Semana 5: memoria dinámica. Se usa dentro del entorno del curso.
#
# make compila los programas de la clase en build/
# make lab compila el laboratorio en build/lab/
# make hunt compila los programas de la cacería en build/hunt/: con sanitizers,
# y con el sufijo _vg para valgrind (-g -O0, sin sanitizers)
# make test corre las pruebas de los ejemplos y de los apuntes
# make test-lab prueba su laboratorio; falla hasta que lo terminen
# make clean borra lo generado
#
# Los programas de errors/ tienen errores a propósito. No se compilan con make:
# los compilan las pruebas, y ustedes, con los comandos de los apuntes.
CC = gcc
CFLAGS = -Wall -Wextra -std=c17 -g -fsanitize=address,undefined
VGFLAGS = -Wall -Wextra -std=c17 -g -O0
BUILD = build
PROGRAMS = reverse_numbers range_block sizes calloc_zero strings_owner student realloc_grow growth_count image_size
HUNT = longest_word seat_list route_backup grade_histogram passing_groups
IMAGE_HEADERS = lab/image.h lab/filters.h
ARRAY_HEADERS = lab/int_array.h
LAB_PROGRAMS = image_tests image_demo str_tests array_tests sum_file
.PHONY: all lab hunt test test-lab clean
all: $(PROGRAMS:%=$(BUILD)/%) $(BUILD)/no_memory
$(BUILD)/%: %.c | $(BUILD)
$(CC) $(CFLAGS) $< -o $@
# Sin sanitizers: con ASan, un pedido imposible detiene el programa antes de que malloc devuelva.
$(BUILD)/no_memory: no_memory.c | $(BUILD)
$(CC) $(VGFLAGS) $< -o $@
$(BUILD)/lab/image_tests: lab/image_tests.c lab/image.c $(IMAGE_HEADERS) | $(BUILD)/lab
$(CC) $(CFLAGS) -Ilab lab/image_tests.c lab/image.c -o $@
$(BUILD)/lab/image_demo: lab/image_demo.c lab/image.c lab/filters.c $(IMAGE_HEADERS) | $(BUILD)/lab
$(CC) $(CFLAGS) -Ilab lab/image_demo.c lab/image.c lab/filters.c -o $@
$(BUILD)/lab/str_tests: lab/str_tests.c lab/str_tools.c lab/str_tools.h | $(BUILD)/lab
$(CC) $(CFLAGS) -Ilab lab/str_tests.c lab/str_tools.c -o $@
$(BUILD)/lab/array_tests: lab/array_tests.c lab/int_array.c $(ARRAY_HEADERS) | $(BUILD)/lab
$(CC) $(CFLAGS) -Ilab lab/array_tests.c lab/int_array.c -o $@
$(BUILD)/lab/sum_file: lab/sum_file.c lab/int_array.c $(ARRAY_HEADERS) | $(BUILD)/lab
$(CC) $(CFLAGS) -Ilab lab/sum_file.c lab/int_array.c -o $@
$(BUILD)/solution/image_tests: solution/image_tests.c solution/image.c $(IMAGE_HEADERS) | $(BUILD)/solution
$(CC) $(CFLAGS) -Ilab solution/image_tests.c solution/image.c -o $@
$(BUILD)/solution/image_demo: lab/image_demo.c solution/image.c lab/filters.c $(IMAGE_HEADERS) | $(BUILD)/solution
$(CC) $(CFLAGS) -Ilab lab/image_demo.c solution/image.c lab/filters.c -o $@
$(BUILD)/solution/str_tests: solution/str_tests.c solution/str_tools.c lab/str_tools.h | $(BUILD)/solution
$(CC) $(CFLAGS) -Ilab solution/str_tests.c solution/str_tools.c -o $@
$(BUILD)/solution/array_tests: solution/array_tests.c solution/int_array.c $(ARRAY_HEADERS) | $(BUILD)/solution
$(CC) $(CFLAGS) -Ilab solution/array_tests.c solution/int_array.c -o $@
$(BUILD)/solution/sum_file: lab/sum_file.c solution/int_array.c $(ARRAY_HEADERS) | $(BUILD)/solution
$(CC) $(CFLAGS) -Ilab lab/sum_file.c solution/int_array.c -o $@
$(BUILD)/hunt/%: lab/hunt/%.c | $(BUILD)/hunt
$(CC) $(CFLAGS) $< -o $@
$(BUILD)/hunt/%_vg: lab/hunt/%.c | $(BUILD)/hunt
$(CC) $(VGFLAGS) $< -o $@
$(BUILD) $(BUILD)/lab $(BUILD)/hunt $(BUILD)/solution:
mkdir -p $@
lab: $(LAB_PROGRAMS:%=$(BUILD)/lab/%)
hunt: $(HUNT:%=$(BUILD)/hunt/%) $(HUNT:%=$(BUILD)/hunt/%_vg)
# Si existe solution/, también se comprueba la solución del laboratorio.
test: all
@if [ -d solution ]; then $(MAKE) --no-print-directory \
$(LAB_PROGRAMS:%=$(BUILD)/solution/%); fi
python3 -m unittest discover -p 'test_*.py'
test-lab: all lab
LAB_DIR=lab python3 -m unittest -v test_lab
clean:
rm -rf $(BUILD) __pycache__