Semana 11 — Biblioteca de cadenas y matrices
Objetivos
- Usar las funciones principales de
string.h. - Declarar matrices y recorrerlas por filas y por columnas.
- Explicar cómo se guarda una matriz en memoria.
Teoría — Biblioteca de cadenas · Matrices
Lectura: Apuntes, semana 11.
Temas:
strlen,strcmp,strncpy,strchrystrstr. Por qué==no compara cadenas.- Matrices: arreglos bidimensionales en memoria, fila por fila.
- Recorrer por filas y por columnas. Ejemplo: notas por estudiante y por curso.
- Pasar una matriz a una función.
- Sumar y transponer matrices.
Laboratorio — Cadenas y matrices
Esta semana la sesión de laboratorio es para la Práctica 3. Las actividades de abajo son trabajo autónomo y no se entregan. Conviene hacerlas antes de la práctica.
- Contar cuántas veces aparece una palabra en un texto con
strstr. - Guardar las notas de varios estudiantes en una matriz. Calcular el promedio por estudiante y por curso.
- Hallar al mejor estudiante de cada curso.
- Transponer una matriz y comprobarlo con
assert.
Entregas
- Práctica 3: Arreglos, cadenas y matrices. Ver
practicas/README.md.
Preguntas de repaso
- ¿Por qué
if (a == b)no compara dos cadenas? ¿Qué compara? - ¿Qué devuelve
strcmpy cómo se usa para ordenar? - ¿Cuántos bytes ocupa
int m[3][4]si cadaintocupa 4? ¿Dónde estám[1][2]si empieza en 1000? - ¿Cómo sumarían cada columna de una matriz? Escríbanlo en pseudocódigo.
- ¿Por qué al pasar una matriz a una función hay que indicar el número de columnas?
Código de la semana
Compilar y probar dentro del contenedor del curso con make y make test.
Ejemplos (12)Errores (18)Pruebas (3)Laboratorio (2)Compilación (1)
Ejemplos
Los programas que usan los apuntes y la clase.
// Agregar al final de una cadena sin pasarse del tamaño del arreglo.
#include <stdio.h>
#include <string.h>
#define BIG 20
#define SMALL 8
// strncat agrega como máximo el número que recibe, y después siempre pone '\0'.
// Por eso se le da lo que queda libre, sin contar el byte del '\0'.
void safe_append(char dest[], const char src[], int size) {
int used = (int) strlen(dest);
strncat(dest, src, size - used - 1);
}
int main(void) {
char big[BIG] = "Ana";
char small[SMALL] = "Ana";
safe_append(big, " Quispe", BIG);
printf("%s\n", big);
safe_append(small, " Quispe", SMALL);
printf("%s\n", small);
return 0;
}// Parte de las pruebas del laboratorio: test_lab.py llama con esto a sus funciones.
// No hace falta leerlo. Usa argumentos de la línea de comandos, que no vemos en el curso.
#include <stdio.h>
#include <stdlib.h>
#define STUDENTS 4
#define COURSES 3
int count_word(const char text[], const char word[]);
void student_averages(int grades[][COURSES], int students, double averages[]);
void course_averages(int grades[][COURSES], int students, double averages[]);
void best_students(int grades[][COURSES], int students, int best[]);
void transpose(int grades[][COURSES], int result[][STUDENTS]);
int is_transpose(int grades[][COURSES], int result[][STUDENTS]);
// Copia el texto a un arreglo propio, para que la función reciba memoria de la pila.
static void copy(char dest[], const char src[], int size) {
int i = 0;
while (src[i] != '\0' && i < size - 1) {
dest[i] = src[i];
i++;
}
dest[i] = '\0';
}
// Lee la matriz de los argumentos, fila por fila, y guarda una copia.
static void read_matrix(char *argv[], int first, int grades[][COURSES], int saved[][COURSES]) {
for (int i = 0; i < STUDENTS; i++) {
for (int j = 0; j < COURSES; j++) {
grades[i][j] = atoi(argv[first + i * COURSES + j]);
saved[i][j] = grades[i][j];
}
}
}
// Avisa si la función cambió la matriz que debía solo leer.
static void check_intact(int grades[][COURSES], int saved[][COURSES]) {
for (int i = 0; i < STUDENTS; i++) {
for (int j = 0; j < COURSES; j++) {
if (grades[i][j] != saved[i][j]) {
printf("la función cambió grades\n");
return;
}
}
}
}
static void print_doubles(const double a[], int n) {
for (int i = 0; i < n; i++) {
printf("%.2f ", a[i]);
}
printf("\n");
}
static int run_words(int argc, char *argv[]) {
char text[128];
char word[64];
if (argc != 4) {
return 2;
}
copy(text, argv[2], 128);
copy(word, argv[3], 64);
printf("%d\n", count_word(text, word));
return 0;
}
static int run_matrix(char op, int argc, char *argv[]) {
int grades[STUDENTS][COURSES];
int saved[STUDENTS][COURSES];
int total_args = 3 + STUDENTS * COURSES;
if (argc < total_args) {
return 2;
}
int students = atoi(argv[2]);
read_matrix(argv, 3, grades, saved);
if (op == 's') {
double averages[STUDENTS] = {-1, -1, -1, -1};
student_averages(grades, students, averages);
print_doubles(averages, STUDENTS);
} else if (op == 'c') {
double averages[COURSES] = {-1, -1, -1};
course_averages(grades, students, averages);
print_doubles(averages, COURSES);
} else if (op == 'b') {
int best[COURSES] = {-1, -1, -1};
best_students(grades, students, best);
printf("%d %d %d\n", best[0], best[1], best[2]);
} else if (op == 't') {
int result[COURSES][STUDENTS];
transpose(grades, result);
for (int j = 0; j < COURSES; j++) {
for (int i = 0; i < STUDENTS; i++) {
printf("%d ", result[j][i]);
}
printf("\n");
}
} else if (op == 'i') {
int other[COURSES][STUDENTS];
if (argc != total_args + STUDENTS * COURSES) {
return 2;
}
for (int j = 0; j < COURSES; j++) {
for (int i = 0; i < STUDENTS; i++) {
other[j][i] = atoi(argv[total_args + j * STUDENTS + i]);
}
}
printf("%d\n", is_transpose(grades, other));
} else {
return 2;
}
check_intact(grades, saved);
return 0;
}
int main(int argc, char *argv[]) {
if (argc < 2) {
return 2;
}
if (argv[1][0] == 'w') {
return run_words(argc, argv);
}
return run_matrix(argv[1][0], argc, argv);
}// strcmp: solo importa el signo de lo que devuelve.
#include <stdio.h>
#include <string.h>
void show_order(const char a[], const char b[]) {
int result = strcmp(a, b);
if (result < 0) {
printf("\"%s\" va antes que \"%s\"\n", a, b);
} else if (result > 0) {
printf("\"%s\" va después de \"%s\"\n", a, b);
} else {
printf("\"%s\" y \"%s\" son iguales\n", a, b);
}
}
int main(void) {
show_order("ana", "ana");
show_order("ana", "ano");
show_order("an", "ana");
show_order("sol", "luna");
show_order("Z", "a");
show_order("z", "ñ");
return 0;
}// strchr y strstr devuelven una dirección, o NULL si no encuentran nada.
#include <stdio.h>
#include <string.h>
int main(void) {
char email[] = "ana@example.com";
char text[] = "el año pasado llovió en Lima";
if (strchr(email, '@') != NULL) {
printf("Tiene @: %s\n", strchr(email, '@'));
}
if (strchr(email, '#') == NULL) {
printf("No tiene #\n");
}
if (strstr(text, "Lima") != NULL) {
printf("Desde Lima: %s\n", strstr(text, "Lima"));
}
if (strstr(text, "lima") == NULL) {
printf("lima, en minúsculas, no aparece\n");
}
if (strstr(text, "ñ") != NULL) {
printf("Desde la ñ: %s\n", strstr(text, "ñ"));
}
// &text[3] es la dirección del byte 3: lo que recibe la función empieza ahí.
printf("Desde el byte 3: %s\n", &text[3]);
if (strstr(text, "el") != NULL) {
printf("Desde el byte 0 hay un el\n");
}
if (strstr(&text[1], "el") == NULL) {
printf("Desde el byte 1 no hay ninguno\n");
}
return 0;
}// Notas de 3 estudiantes en 4 cursos: promedios por fila y por columna.
#include <stdio.h>
#define STUDENTS 3
#define COURSES 4
int main(void) {
int grades[STUDENTS][COURSES] = {
{14, 9, 17, 12},
{11, 16, 8, 13},
{18, 10, 15, 20},
};
// Por filas: cada estudiante, y dentro de él, cada curso.
for (int i = 0; i < STUDENTS; i++) {
int total = 0;
for (int j = 0; j < COURSES; j++) {
total = total + grades[i][j];
}
printf("Estudiante %d: suma %d, promedio %.2f\n", i, total, (double) total / COURSES);
}
// Por columnas: cada curso, y dentro de él, cada estudiante.
for (int j = 0; j < COURSES; j++) {
int total = 0;
for (int i = 0; i < STUDENTS; i++) {
total = total + grades[i][j];
}
printf("Curso %d: suma %d, promedio %.2f\n", j, total, (double) total / STUDENTS);
}
return 0;
}// strlen cuenta bytes hasta el '\0'. Devuelve size_t, un tipo sin signo.
#include <stdio.h>
#include <string.h>
int main(void) {
char t[8] = "hola";
printf("strlen(t) = %zu\n", strlen(t));
printf("sizeof(t) = %zu\n", sizeof(t));
printf("strlen(\"\") = %zu\n", strlen(""));
printf("strlen(\"año\") = %zu\n", strlen("año"));
// Para recorrer con un int, se guarda la longitud una vez, con un cast.
int n = (int) strlen(t);
for (int i = n - 1; i >= 0; i--) {
printf("%c", t[i]);
}
printf("\n");
return 0;
}// Sumar dos matrices del mismo tamaño, casilla por casilla.
#include <stdio.h>
#define ROWS 2
#define COLS 3
void add_matrices(int a[][COLS], int b[][COLS], int result[][COLS], int rows) {
for (int i = 0; i < rows; i++) {
for (int j = 0; j < COLS; j++) {
result[i][j] = a[i][j] + b[i][j];
}
}
}
int main(void) {
int a[ROWS][COLS] = {{1, 2, 3}, {4, 5, 6}};
int b[ROWS][COLS] = {{10, 20, 30}, {40, 50, 60}};
int result[ROWS][COLS];
add_matrices(a, b, result, ROWS);
for (int i = 0; i < ROWS; i++) {
for (int j = 0; j < COLS; j++) {
printf("%3d", result[i][j]);
}
printf("\n");
}
return 0;
}// Una matriz se guarda fila por fila, con los elementos seguidos.
#include <stdio.h>
#define ROWS 3
#define COLS 4
int main(void) {
int m[ROWS][COLS] = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12},
};
printf("sizeof(m) = %zu\n", sizeof(m));
printf("sizeof(m[0]) = %zu\n", sizeof(m[0]));
printf("sizeof(m[0][0]) = %zu\n", sizeof(m[0][0]));
printf("m[1][2] = %d\n", m[1][2]);
for (int i = 0; i < ROWS; i++) {
for (int j = 0; j < COLS; j++) {
printf("m[%d][%d] = %2d está en %p\n", i, j, m[i][j], (void *) &m[i][j]);
}
}
return 0;
}// Pasar una matriz a una función: las columnas van en el tipo, las filas en un parámetro.
#include <stdio.h>
#define COLS 4
void print_matrix(int m[][COLS], int rows) {
for (int i = 0; i < rows; i++) {
for (int j = 0; j < COLS; j++) {
printf("%3d", m[i][j]);
}
printf("\n");
}
}
int column_sum(int m[][COLS], int rows, int column) {
int total = 0;
for (int i = 0; i < rows; i++) {
total = total + m[i][column];
}
return total;
}
// Como con un arreglo, la función trabaja sobre la matriz original.
void add_bonus(int m[][COLS], int rows, int bonus) {
for (int i = 0; i < rows; i++) {
for (int j = 0; j < COLS; j++) {
m[i][j] = m[i][j] + bonus;
}
}
}
int main(void) {
int grades[3][COLS] = {
{14, 9, 17, 12},
{11, 16, 8, 13},
{18, 10, 15, 20},
};
print_matrix(grades, 3);
printf("Suma del curso 0: %d\n", column_sum(grades, 3, 0));
printf("Suma del curso 0, solo 2 filas: %d\n", column_sum(grades, 2, 0));
add_bonus(grades, 2, 1);
print_matrix(grades, 3);
return 0;
}// Un arreglo de cadenas es una matriz de char: cada fila guarda una cadena.
#include <stdio.h>
#include <string.h>
#define COUNT 4
#define NAME_SIZE 12
// Requiere size >= 1. Es la copia segura de safe_copy.c.
void safe_copy(char dest[], const char src[], int size) {
strncpy(dest, src, size - 1);
dest[size - 1] = '\0';
}
// Devuelve el índice de la cadena que va primero según strcmp.
int first_name(char names[][NAME_SIZE], int n) {
int first = 0;
for (int i = 1; i < n; i++) {
if (strcmp(names[i], names[first]) < 0) {
first = i;
}
}
return first;
}
int main(void) {
char names[COUNT][NAME_SIZE] = {"Rosa", "Ana", "Luis", "Zoila"};
printf("sizeof(names) = %zu\n", sizeof(names));
for (int i = 0; i < COUNT; i++) {
printf("names[%d] = %s, %zu bytes de texto\n", i, names[i], strlen(names[i]));
}
printf("Primero: %s\n", names[first_name(names, COUNT)]);
safe_copy(names[1], "Maria de los Angeles", NAME_SIZE);
printf("names[1] = %s\n", names[1]);
printf("Primero: %s\n", names[first_name(names, COUNT)]);
return 0;
}// Copiar con strncpy sin perder el '\0': se copia size - 1 y se pone a mano.
#include <stdio.h>
#include <string.h>
#define SIZE 5
// Requiere size >= 1. Da el mismo texto que copy_text; además, strncpy rellena lo que sobra.
void safe_copy(char dest[], const char src[], int size) {
strncpy(dest, src, size - 1);
dest[size - 1] = '\0';
}
int main(void) {
char word[SIZE];
safe_copy(word, "sol", SIZE);
printf("%s\n", word);
safe_copy(word, "luna", SIZE);
printf("%s\n", word);
safe_copy(word, "estrella", SIZE);
printf("%s\n", word);
return 0;
}// Lo que strncpy deja en el destino: a veces sin '\0', a veces con muchos.
#include <stdio.h>
#include <string.h>
#define SIZE 5
// Escribe los bytes como números. No usa %s: el arreglo puede no ser una cadena.
void print_bytes(const char a[], int size) {
for (int i = 0; i < size; i++) {
printf(" %d", a[i]);
}
printf("\n");
}
void fill(char dest[], const char src[]) {
strncpy(dest, src, SIZE);
}
int main(void) {
char word[SIZE];
fill(word, "sol");
print_bytes(word, SIZE);
fill(word, "luna");
print_bytes(word, SIZE);
fill(word, "estrella");
print_bytes(word, SIZE);
return 0;
}Errores
Ejemplos con errores a propósito: no compilan, fallan o dan un resultado incorrecto. Los apuntes explican cada uno.
// const en un parámetro matriz: gcc lo acepta, pero C17 no lo permite.
#include <stdio.h>
#define COLS 3
int first(const int m[][COLS]) {
return m[0][0];
}
int main(void) {
int m[2][COLS] = {{1, 2, 3}, {4, 5, 6}};
printf("%d\n", first(m));
return 0;
}// Error: == con dos arreglos compara sus direcciones, no su contenido.
#include <stdio.h>
int main(void) {
char a[] = "hola";
char b[] = "hola";
if (a == b) {
printf("iguales\n");
} else {
printf("distintas\n");
}
return 0;
}// Error: C no dice si dos literales iguales están en el mismo lugar.
#include <stdio.h>
int main(void) {
printf("%d\n", "hola" == "hola");
return 0;
}// Error: dentro de una función, == no da ninguna advertencia. Sigue comparando direcciones.
#include <stdio.h>
int same_text(const char a[], const char b[]) {
return a == b;
}
int main(void) {
char a[] = "hola";
char b[] = "hola";
printf("a con b: %d\n", same_text(a, b));
printf("a con a: %d\n", same_text(a, a));
return 0;
}// Error: con la cadena vacía, s[strlen(s) - 1] está fuera del arreglo.
#include <stdio.h>
#include <string.h>
char last_char(const char s[]) {
return s[strlen(s) - 1];
}
int main(void) {
char word[] = "sol";
char empty[] = "";
printf("%c\n", last_char(word));
printf("%c\n", last_char(empty));
return 0;
}// Error: una columna que no existe. Los índices se leen de la entrada.
#include <stdio.h>
int main(void) {
int m[2][3] = {{1, 2, 3}, {4, 5, 6}};
int j;
if (scanf("%d", &j) != 1) {
printf("Entrada inválida\n");
return 1;
}
printf("m[0][%d] = %d\n", j, m[0][j]);
return 0;
}// Error: m[1, 2] no es la casilla de la fila 1 y la columna 2.
#include <stdio.h>
int main(void) {
int m[3][4] = {{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}};
printf("%d\n", m[1, 2]);
return 0;
}// Error: el parámetro no dice cuántas columnas tiene la matriz.
#include <stdio.h>
int first(int m[][]) {
return m[0][0];
}
int main(void) {
int m[2][3] = {{1, 2, 3}, {4, 5, 6}};
printf("%d\n", first(m));
return 0;
}// Error: la función espera 3 columnas y la matriz tiene 4.
#include <stdio.h>
int first(int m[][3]) {
return m[0][0];
}
int main(void) {
int m[2][4] = {{1, 2, 3, 4}, {5, 6, 7, 8}};
printf("%d\n", first(m));
return 0;
}// Advertencia: comparar un int con el resultado de strlen mezcla signo y sin signo.
#include <stdio.h>
#include <string.h>
int main(void) {
char s[] = "sol";
for (int i = 0; i < strlen(s); i++) {
printf("%c\n", s[i]);
}
return 0;
}// Error: strcat no recibe el tamaño del destino. Compila sin advertencias.
#include <stdio.h>
#include <string.h>
int main(void) {
char word[20];
char full[8] = "Ana ";
if (scanf("%19s", word) != 1) {
printf("Entrada inválida\n");
return 1;
}
strcat(full, word);
printf("%s\n", full);
return 0;
}// Error: el origen y el destino de strcat no pueden pisarse.
#include <stdio.h>
#include <string.h>
int main(void) {
char s[20] = "hola";
strcat(s, s);
printf("%s\n", s);
return 0;
}// Error: strcmp devuelve 0 si son iguales, y en un if el 0 es falso.
#include <stdio.h>
#include <string.h>
int main(void) {
char a[] = "hola";
char b[] = "hola";
if (strcmp(a, b)) {
printf("iguales\n");
} else {
printf("distintas\n");
}
return 0;
}// Error: strlen da un valor sin signo. Restar 1 a un 0 da la vuelta.
#include <stdio.h>
#include <string.h>
int main(void) {
char empty[] = "";
printf("strlen(empty) - 1 = %zu\n", strlen(empty) - 1);
if (strlen(empty) - 1 >= 0) {
printf("Esta condición siempre se cumple\n");
}
return 0;
}// Error: si el texto no cabe, strncpy no pone '\0', y %s sigue de largo.
#include <stdio.h>
#include <string.h>
#define SIZE 5
void fill(char dest[], const char src[]) {
strncpy(dest, src, SIZE);
}
int main(void) {
char word[SIZE];
fill(word, "estrella");
printf("%s\n", word);
return 0;
}// Si el compilador ve el texto, avisa que strncpy lo va a cortar.
#include <stdio.h>
#include <string.h>
int main(void) {
char word[5];
strncpy(word, "estrella", 5);
printf("%c\n", word[0]);
return 0;
}// Error: usar lo que devuelve strstr sin revisar si es NULL.
#include <stdio.h>
#include <string.h>
int main(void) {
char text[] = "sol y luna";
printf("%s\n", strstr(text, "luna"));
printf("%s\n", strstr(text, "mar"));
return 0;
}// Error: al transponer en el mismo arreglo, cada par se cambia dos veces.
#include <stdio.h>
#define N 3
void transpose_wrong(int m[][N]) {
for (int i = 0; i < N; i++) {
for (int j = 0; j < N; j++) {
int saved = m[i][j];
m[i][j] = m[j][i];
m[j][i] = saved;
}
}
}
int main(void) {
int m[N][N] = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};
transpose_wrong(m);
for (int i = 0; i < N; i++) {
printf("%d %d %d\n", m[i][0], m[i][1], m[i][2]);
}
return 0;
}Pruebas
Comprueban lo que afirman los apuntes; se ejecutan con make test.
"""Comprueba lo que los apuntes afirman sobre los programas de la semana 11."""
import pathlib
import re
import subprocess
import tempfile
import unittest
HERE = pathlib.Path(__file__).parent
BUILD = HERE / "build"
FLAGS = ["-Wall", "-Wextra", "-std=c17"]
SANITIZE = ["-g", "-fsanitize=address,undefined"]
def execute(command, stdin=""):
done = subprocess.run(
command, input=stdin, encoding="utf-8", errors="replace",
stdout=subprocess.PIPE, stderr=subprocess.STDOUT, timeout=10,
)
return done.stdout, done.returncode
def run(program, stdin=""):
"""Ejecuta un programa de build/; devuelve su salida (con errores) y su estado."""
return execute([str(BUILD / program)], stdin)
def build_source(source, *extra):
"""Compila un archivo; devuelve los mensajes, el estado y el ejecutable."""
out = pathlib.Path(tempfile.mkdtemp()) / pathlib.Path(source).stem
done = subprocess.run(
["gcc", *FLAGS, *extra, source, "-o", str(out)], cwd=HERE, encoding="utf-8",
stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
)
return done.stdout, done.returncode, out
class CleanCompile(unittest.TestCase):
def test_examples_have_no_warnings(self):
# Se compila de verdad: -Wstringop-truncation no sale solo revisando la sintaxis.
for source in sorted(HERE.glob("*.c")):
with self.subTest(source=source.name):
messages, status, _ = build_source(
source.name, *SANITIZE, "-Werror", *(["-c"] if source.name == "check_lab.c" else []))
self.assertEqual(status, 0, messages)
def test_no_pointer_variables_or_pointer_arithmetic_in_examples(self):
# La GUIA deja los punteros para cursos posteriores.
for source in sorted(HERE.glob("*.c")) + sorted((HERE / "solution").glob("*.c")):
if source.name == "check_lab.c":
continue
code = re.sub(r"//[^\n]*", "", source.read_text(encoding="utf-8"))
with self.subTest(source=source.name):
self.assertNotRegex(code, r"\b(?:char|int|double)\s*\*\s*\w")
self.assertNotRegex(code, r"str(?:str|chr)\([^;]*\)\s*[-+]")
class Lengths(unittest.TestCase):
def test_lengths(self):
self.assertEqual(run("lengths"), (
'strlen(t) = 4\nsizeof(t) = 8\nstrlen("") = 0\nstrlen("año") = 4\naloh\n', 0))
def test_subtracting_from_zero_wraps(self):
messages, status, binary = build_source("errors/strlen_wrap.c")
self.assertEqual(status, 0)
self.assertIn("-Wtype-limits", messages)
output, _ = execute([str(binary)])
value = int(re.search(r"= (\d+)", output).group(1))
self.assertGreater(value, 2 ** 31) # el valor exacto depende del tamaño de size_t
self.assertEqual((value + 1) & value, 0) # es 2^n - 1
self.assertIn("siempre se cumple", output)
def test_last_char_of_empty_string_is_reported(self):
messages, status, binary = build_source("errors/last_char.c", *SANITIZE)
self.assertEqual(status, 0)
self.assertEqual(messages, "") # gcc no avisa
output, code = execute([str(binary)])
self.assertIn("AddressSanitizer", output)
self.assertNotEqual(code, 0)
def test_sign_compare_warning(self):
messages, status, _ = build_source("errors/sign_compare.c")
self.assertEqual(status, 0)
self.assertIn("-Wsign-compare", messages)
class Comparing(unittest.TestCase):
def test_equal_arrays(self):
messages, status, binary = build_source("errors/equal_arrays.c")
self.assertEqual(status, 0)
self.assertIn("-Warray-compare", messages)
self.assertEqual(execute([str(binary)]), ("distintas\n", 0))
def test_equal_params_is_silent(self):
messages, status, binary = build_source("errors/equal_params.c")
self.assertEqual((messages, status), ("", 0))
self.assertEqual(execute([str(binary)]), ("a con b: 0\na con a: 1\n", 0))
def test_equal_literals_warns_and_its_result_is_not_tested(self):
messages, status, binary = build_source("errors/equal_literals.c")
self.assertEqual(status, 0)
self.assertIn("unspecified behavior", messages)
output, _ = execute([str(binary)])
self.assertIn(output, ("0\n", "1\n"))
def test_strcmp_order(self):
output, code = run("compare")
self.assertEqual(code, 0)
self.assertEqual(output.splitlines(), [
'"ana" y "ana" son iguales',
'"ana" va antes que "ano"',
'"an" va antes que "ana"',
'"sol" va después de "luna"',
'"Z" va antes que "a"',
'"z" va antes que "ñ"',
])
def test_strcmp_order_does_not_depend_on_char_signedness(self):
expected, _ = run("compare")
for option in ["-fsigned-char", "-funsigned-char"]:
with self.subTest(option=option):
_, status, binary = build_source("compare.c", option)
self.assertEqual(status, 0)
self.assertEqual(execute([str(binary)]), (expected, 0))
def test_strcmp_in_an_if_reads_backwards(self):
_, status, binary = build_source("errors/strcmp_wrong.c")
self.assertEqual(status, 0)
self.assertEqual(execute([str(binary)]), ("distintas\n", 0))
class Copying(unittest.TestCase):
def test_strncpy_bytes(self):
output, code = run("strncpy_bytes")
self.assertEqual(code, 0)
rows = [line.split() for line in output.splitlines()]
self.assertEqual(rows[0], ["115", "111", "108", "0", "0"]) # sol, con relleno
self.assertEqual(rows[1], ["108", "117", "110", "97", "0"]) # luna, justo
self.assertEqual(rows[2], ["101", "115", "116", "114", "101"]) # sin '\0'
self.assertNotIn("0", rows[2])
def test_gcc_warns_only_when_it_sees_the_text(self):
messages, status, _ = build_source("errors/strncpy_warn.c")
self.assertEqual(status, 0)
self.assertIn("-Wstringop-truncation", messages)
messages, status, _ = build_source("errors/strncpy_no_null.c")
self.assertEqual((messages, status), ("", 0))
def test_strncpy_without_terminator_is_reported(self):
_, status, binary = build_source("errors/strncpy_no_null.c", *SANITIZE)
self.assertEqual(status, 0)
output, code = execute([str(binary)])
self.assertIn("AddressSanitizer: stack-buffer-overflow", output)
self.assertIn("READ of size", output)
self.assertNotEqual(code, 0)
def test_safe_copy(self):
self.assertEqual(run("safe_copy"), ("sol\nluna\nestr\n", 0))
def test_append(self):
self.assertEqual(run("append"), ("Ana Quispe\nAna Qui\n", 0))
def test_strcat_overflows_only_with_long_input(self):
messages, status, binary = build_source("errors/strcat_overflow.c", *SANITIZE)
self.assertEqual((messages, status), ("", 0))
self.assertEqual(execute([str(binary)], "Li\n"), ("Ana Li\n", 0))
output, code = execute([str(binary)], "Quispe_Huaman\n")
self.assertIn("AddressSanitizer: stack-buffer-overflow", output)
self.assertIn("WRITE of size 14", output)
self.assertNotEqual(code, 0)
self.assertEqual(execute([str(binary)], ""), ("Entrada inválida\n", 1))
def test_strcat_overlap(self):
messages, status, binary = build_source("errors/strcat_overlap.c", *SANITIZE)
self.assertEqual(status, 0)
self.assertIn("-Wrestrict", messages)
output, code = execute([str(binary)])
self.assertIn("strcat-param-overlap", output)
self.assertNotEqual(code, 0)
class Searching(unittest.TestCase):
def test_find_text(self):
self.assertEqual(run("find_text"), (
"Tiene @: @example.com\nNo tiene #\nDesde Lima: Lima\n"
"lima, en minúsculas, no aparece\nDesde la ñ: ño pasado llovió en Lima\n"
"Desde el byte 3: año pasado llovió en Lima\n"
"Desde el byte 0 hay un el\nDesde el byte 1 no hay ninguno\n", 0))
def test_null_result_used_without_checking_does_not_exit_cleanly(self):
# Es comportamiento indefinido: no se prueba cómo muere, solo con el sanitizer.
_, status, binary = build_source("errors/strstr_null.c", *SANITIZE)
self.assertEqual(status, 0)
output, code = execute([str(binary)])
self.assertNotEqual(code, 0)
self.assertIn("null", output)
class Matrices(unittest.TestCase):
def test_layout_sizes(self):
output, code = run("matrix_layout")
self.assertEqual(code, 0)
self.assertIn("sizeof(m) = 48\nsizeof(m[0]) = 16\nsizeof(m[0][0]) = 4\nm[1][2] = 7\n", output)
def test_layout_is_row_by_row(self):
output, _ = run("matrix_layout")
cells = re.findall(r"m\[(\d)\]\[(\d)\] = +(\d+) está en (0x[0-9a-f]+)", output)
self.assertEqual(len(cells), 12)
self.assertEqual([int(c[2]) for c in cells], list(range(1, 13)))
addresses = [int(c[3], 16) for c in cells]
for row, column, _, _ in cells:
position = int(row) * 4 + int(column) # la fórmula de los apuntes
self.assertEqual(addresses[position] - addresses[0], position * 4)
def test_grades_table(self):
self.assertEqual(run("grades_table")[0].splitlines(), [
"Estudiante 0: suma 52, promedio 13.00",
"Estudiante 1: suma 48, promedio 12.00",
"Estudiante 2: suma 63, promedio 15.75",
"Curso 0: suma 43, promedio 14.33",
"Curso 1: suma 35, promedio 11.67",
"Curso 2: suma 40, promedio 13.33",
"Curso 3: suma 45, promedio 15.00",
])
def test_matrix_param_changes_only_the_rows_it_receives(self):
lines = run("matrix_param")[0].splitlines()
self.assertEqual(lines[3:5], ["Suma del curso 0: 43", "Suma del curso 0, solo 2 filas: 25"])
self.assertEqual(lines[5:], [" 15 10 18 13", " 12 17 9 14", " 18 10 15 20"])
def test_missing_columns_do_not_compile(self):
messages, status, _ = build_source("errors/matrix_no_columns.c")
self.assertNotEqual(status, 0)
self.assertIn("must have bounds for all dimensions except the first", messages)
def test_wrong_columns_are_noticed(self):
messages, _, _ = build_source("errors/matrix_wrong_columns.c")
self.assertIn("incompatible pointer type", messages)
def test_const_matrix_is_only_seen_with_pedantic(self):
messages, status, _ = build_source("errors/const_matrix.c")
self.assertEqual((messages, status), ("", 0))
messages, status, _ = build_source("errors/const_matrix.c", "-Wpedantic")
self.assertEqual(status, 0)
self.assertIn("before C23", messages)
def test_no_example_uses_const_matrix_parameters(self):
for source in sorted(HERE.glob("*.c")) + sorted((HERE / "solution").glob("*.c")):
with self.subTest(source=source.name):
self.assertNotRegex(source.read_text(encoding="utf-8"), r"const\s+\w+\s+\w+\[\]\[")
def test_comma_warns(self):
messages, status, _ = build_source("errors/matrix_comma.c")
self.assertEqual(status, 0)
self.assertIn("left-hand operand of comma", messages)
def test_column_out_of_bounds_seen_by_undefined_not_by_address(self):
_, status, binary = build_source("errors/matrix_bounds.c", *SANITIZE)
self.assertEqual(status, 0)
self.assertEqual(execute([str(binary)], "2\n"), ("m[0][2] = 3\n", 0))
output, _ = execute([str(binary)], "3\n")
self.assertIn("runtime error: index 3 out of bounds for type 'int [3]'", output)
self.assertNotIn("AddressSanitizer", output)
# Solo con address, nada avisa en este acceso.
_, status, binary = build_source("errors/matrix_bounds.c", "-g", "-fsanitize=address")
self.assertEqual(status, 0)
output, _ = execute([str(binary)], "3\n")
self.assertNotIn("AddressSanitizer", output)
self.assertEqual(execute([str(binary)], "x\n"), ("Entrada inválida\n", 1))
def test_names(self):
self.assertEqual(run("names"), (
"sizeof(names) = 48\n"
"names[0] = Rosa, 4 bytes de texto\nnames[1] = Ana, 3 bytes de texto\n"
"names[2] = Luis, 4 bytes de texto\nnames[3] = Zoila, 5 bytes de texto\n"
"Primero: Ana\nnames[1] = Maria de lo\nPrimero: Luis\n", 0))
def test_matrix_add(self):
self.assertEqual(run("matrix_add"), (" 11 22 33\n 44 55 66\n", 0))
def test_transposing_in_place_over_every_cell_changes_nothing(self):
messages, status, binary = build_source("errors/transpose_in_place.c")
self.assertEqual((messages, status), ("", 0))
self.assertEqual(execute([str(binary)]), ("1 2 3\n4 5 6\n7 8 9\n", 0))
if __name__ == "__main__":
unittest.main()"""Pruebas del laboratorio. LAB_DIR dice dónde está el código: lab o solution."""
import os
import pathlib
import re
import subprocess
import tempfile
import unittest
HERE = pathlib.Path(__file__).parent
LAB_DIR = os.environ.get("LAB_DIR", "solution")
# Cuatro filas de tres cursos. Las notas 99 están en una fila que no se usa: solo importan
# si la función lee más filas de las que recibió.
GRADES = [14, 9, 17,
11, 16, 8,
18, 10, 15,
12, 20, 7]
THREE_ROWS = [14, 9, 17,
11, 16, 8,
18, 10, 15,
99, 99, 99]
TIED = [14, 0, 20,
14, 0, 20,
10, 0, 5,
13, 0, 20]
TRANSPOSED = [14, 11, 18, 12,
9, 16, 10, 20,
17, 8, 15, 7]
def run(program):
done = subprocess.run(
[str(HERE / "build" / LAB_DIR / program)], encoding="utf-8", errors="replace",
stdin=subprocess.DEVNULL, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, timeout=10,
)
return done.stdout, done.returncode
def call(*args):
"""Llama a una función con check_lab y devuelve lo que imprime, sin los bordes."""
done = subprocess.run(
[str(HERE / "build" / LAB_DIR / "check_lab")] + [str(a).encode("utf-8") for a in args],
stdin=subprocess.DEVNULL, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, timeout=10,
)
return done.stdout.decode("utf-8", "replace").strip()
def code_without_comments(name):
text = (HERE / LAB_DIR / name).read_text(encoding="utf-8")
return re.sub(r"//[^\n]*", "", text)
@unittest.skipUnless((HERE / LAB_DIR).is_dir(), f"no existe {LAB_DIR}/")
class LabTests(unittest.TestCase):
def test_no_warnings(self):
# Se compila de verdad: algunas advertencias no salen solo revisando la sintaxis.
with tempfile.TemporaryDirectory() as folder:
for name in ["words", "grades"]:
with self.subTest(name=name):
done = subprocess.run(
["gcc", "-Wall", "-Wextra", "-std=c17", "-Werror", "-c",
str(HERE / LAB_DIR / f"{name}.c"), "-o", f"{folder}/{name}.o"],
text=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
)
self.assertEqual(done.returncode, 0, done.stdout)
def test_your_own_asserts_pass(self):
for name in ["words", "grades"]:
with self.subTest(name=name):
self.assertEqual(run(name), ("Todas las pruebas pasaron\n", 0))
def test_count_word_uses_strstr(self):
self.assertRegex(code_without_comments("words.c"), r"\bstrstr\s*\(")
def test_grades_main_checks_with_assert(self):
self.assertRegex(code_without_comments("grades.c"), r"assert\s*\(\s*is_transpose\s*\(")
def test_constants_were_not_changed(self):
code = code_without_comments("grades.c")
self.assertRegex(code, r"#define\s+STUDENTS\s+4\b")
self.assertRegex(code, r"#define\s+COURSES\s+3\b")
def test_count_word(self):
cases = [
("sol y luna", "sol", "1"),
("sol y sol", "sol", "2"),
("luna y sol", "sol", "1"), # al final
("sol", "sol", "1"), # el texto completo
("solsolsol", "sol", "3"), # seguidas
("sol y luna", "mar", "0"),
("so", "sol", "0"), # la palabra es más larga que el texto
("", "sol", "0"),
("Sol sol SOL", "sol", "1"), # byte por byte
("girasol y sol", "sol", "2"), # no hace falta una palabra completa
("banana", "ana", "1"), # no se pisan
("aaaaa", "aa", "2"),
("año y ñandú", "ñ", "2"), # bytes de UTF-8
]
for text, word, expected in cases:
with self.subTest(text=text, word=word):
self.assertEqual(call("w", text, word), expected)
def test_count_word_with_empty_word(self):
for text in ["", "sol"]:
with self.subTest(text=text):
self.assertEqual(call("w", text, ""), "0")
def test_student_averages(self):
self.assertEqual(call("s", 4, *GRADES), "13.33 11.67 14.33 13.00")
def test_student_averages_uses_the_rows_it_receives(self):
# La cuarta casilla no se debe tocar: check_lab la deja en -1.
self.assertEqual(call("s", 3, *THREE_ROWS), "13.33 11.67 14.33 -1.00")
def test_course_averages(self):
self.assertEqual(call("c", 4, *GRADES), "13.75 13.75 11.75")
def test_course_averages_uses_the_rows_it_receives(self):
self.assertEqual(call("c", 3, *THREE_ROWS), "14.33 11.67 13.33")
self.assertEqual(call("c", 1, *THREE_ROWS), "14.00 9.00 17.00")
def test_best_students(self):
self.assertEqual(call("b", 4, *GRADES), "2 3 0")
def test_best_students_uses_the_rows_it_receives(self):
self.assertEqual(call("b", 3, *THREE_ROWS), "2 1 0")
self.assertEqual(call("b", 1, *THREE_ROWS), "0 0 0")
def test_best_students_ties_and_zeros(self):
# Empates: gana el de índice menor. El curso 1 tiene solo ceros, y aun así hay mejor.
self.assertEqual(call("b", 4, *TIED), "0 0 0")
def test_best_students_restarts_in_each_course(self):
# Si el máximo no se reinicia, el 20 del curso 0 tapa a los demás cursos.
self.assertEqual(call("b", 4, 20, 1, 2, 3, 5, 6, 4, 9, 8, 7, 8, 9), "0 2 3")
def test_transpose(self):
expected = "14 11 18 12 \n9 16 10 20 \n17 8 15 7"
self.assertEqual(call("t", 4, *GRADES), expected)
def test_is_transpose_accepts_the_right_one(self):
self.assertEqual(call("i", 4, *GRADES, *TRANSPOSED), "1")
def test_is_transpose_finds_any_wrong_cell(self):
for position in [0, 5, 11]:
wrong = list(TRANSPOSED)
wrong[position] = 0
with self.subTest(position=position):
self.assertEqual(call("i", 4, *GRADES, *wrong), "0")
def test_is_transpose_rejects_the_same_values_in_rows(self):
# Las mismas notas sin transponer: fila por fila, igual que grades.
self.assertEqual(call("i", 4, *GRADES, *GRADES), "0")
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 pathlib
import re
import subprocess
import unittest
HERE = pathlib.Path(__file__).parent
ROOT = HERE.parent.parent
NOTES = ROOT / "notes" / "week-11.md"
SLIDES = ROOT / "slides" / "week-11.tex"
# Antes de un bloque con direcciones o con comportamiento indefinido. De él solo se
# exigen estas cadenas estables, si el bloque las muestra.
MACHINE_DEPENDENT = "<!-- depende de la corrida -->"
STABLE = [
"AddressSanitizer: stack-buffer-overflow",
"READ of size 1",
"AddressSanitizer: strcat-param-overlap",
"runtime error: index 3 out of bounds for type 'int [3]'",
"warning: comparison with string literal results in unspecified behavior",
"-Wrestrict",
"sizeof(m) = 48",
"m[1][2] = 7",
]
# Antes de una salida recortada. Sus líneas deben aparecer en la real, y en el mismo orden.
TRIMMED = "<!-- recortado -->"
# En esta salida, solo las direcciones cambian.
ADDRESSES = re.compile(r"0x[0-9a-f]+")
def normalize(text):
# Según el idioma del entorno, gcc usa comillas tipográficas o rectas.
return ADDRESSES.sub("0x", text.replace("‘", "'").replace("’", "'"))
def run_shell(command):
done = subprocess.run(
["bash", "-c", command], cwd=HERE, encoding="utf-8", errors="replace",
stdin=subprocess.DEVNULL, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, timeout=60,
)
return normalize(done.stdout)
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. La marca es el comentario
que lo precede, o un texto cualquiera si no tiene."""
blocks, current, flagged, 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, flagged = [], previous.strip()
elif line.strip():
previous = line
elif line.strip() == "```":
blocks.append((current, flagged))
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()
for source in list(HERE.glob("*.c")) + list((HERE / "errors").glob("*.c")):
lines.update(line.strip() for line in source.read_text(encoding="utf-8").splitlines())
return lines
class NotesAreTrue(unittest.TestCase):
@unittest.skipUnless(NOTES.exists(), "faltan los apuntes")
def test_terminal_blocks_in_notes(self):
checked = 0
for lines, flagged in note_blocks("text"):
if not lines or not lines[0].startswith("$ "):
continue
for command, expected in split_commands(lines):
actual = run_shell(command).rstrip("\n")
checked += 1
with self.subTest(command=command):
if flagged == MACHINE_DEPENDENT:
for token in STABLE:
if any(token in line for line in expected):
self.assertIn(token, actual)
elif flagged == TRIMMED:
real = [line.rstrip() for line in actual.split("\n")]
position = 0
for line in expected:
found = [i for i in range(position, len(real)) if real[i] == line]
self.assertTrue(found, f"no aparece en la salida real: {line}")
position = found[0] + 1
else:
lines = [line.rstrip() for line in actual.split("\n")]
self.assertEqual("\n".join(lines), "\n".join(expected))
self.assertGreater(checked, 20)
@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(), "faltan los apuntes")
def test_notes_show_the_report_and_flag_undefined_behavior(self):
text = NOTES.read_text(encoding="utf-8")
self.assertIn("WRITE of size 14", text)
self.assertIn("en esta corrida", " ".join(text.split()))
class SlidesAreTrue(unittest.TestCase):
@unittest.skipUnless(SLIDES.exists(), "faltan las diapositivas")
def test_terminal_blocks_in_slides(self):
for block in slide_blocks("terminal|salidalarga"):
for command, expected in split_commands(block):
actual = run_shell(command).splitlines()
position = 0
# Una diapositiva puede omitir líneas, pero no cambiarlas ni reordenarlas.
for line in expected:
line = line.strip()
if not line:
continue
with self.subTest(command=command, line=line):
found = [i for i in range(position, len(actual)) if actual[i].strip() == line]
self.assertTrue(found, f"no aparece en la salida real: {line}")
if found:
position = found[0] + 1
@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.
// Laboratorio, partes 2, 3 y 4: notas en una matriz.
//
// Cada fila es un estudiante y cada columna un curso. Las funciones reciben students, las
// filas que están en uso, que van de 1 a STUDENTS. No cambien STUDENTS ni COURSES.
// Borren las líneas (void): solo están para que el archivo compile sin advertencias.
//
// En main hay algunas pruebas. Agreguen más con assert: casos normales y bordes.
//
// Pruébenlo con make test-lab.
#include <assert.h>
#include <stdio.h>
#define STUDENTS 4
#define COURSES 3
// Guarda en averages[i] el promedio de las notas del estudiante i.
void student_averages(int grades[][COURSES], int students, double averages[]) {
(void) grades;
(void) students;
(void) averages;
}
// Guarda en averages[j] el promedio de las notas del curso j.
void course_averages(int grades[][COURSES], int students, double averages[]) {
(void) grades;
(void) students;
(void) averages;
}
// Guarda en best[j] el índice del estudiante con la nota más alta del curso j.
// Si hay un empate, gana el de índice menor.
void best_students(int grades[][COURSES], int students, int best[]) {
(void) grades;
(void) students;
(void) best;
}
// Guarda en result la transpuesta de grades, que tiene STUDENTS filas: la fila i de
// grades pasa a ser la columna i de result.
void transpose(int grades[][COURSES], int result[][STUDENTS]) {
(void) grades;
(void) result;
}
// Devuelve 1 si result es la transpuesta de grades, y 0 si no.
// Revisa todas las casillas.
int is_transpose(int grades[][COURSES], int result[][STUDENTS]) {
(void) grades;
(void) result;
return 0;
}
// Devuelve 1 si a y b se diferencian en menos de 0.001. Dos double no se comparan con ==.
int close_to(double a, double b) {
double difference = a - b;
return difference < 0.001 && difference > -0.001;
}
int main(void) {
int grades[STUDENTS][COURSES] = {
{14, 9, 17},
{11, 16, 8},
{18, 10, 15},
{12, 20, 7},
};
double by_student[STUDENTS] = {0};
double by_course[COURSES] = {0};
int best[COURSES] = {0};
int result[COURSES][STUDENTS] = {{0}};
student_averages(grades, STUDENTS, by_student);
assert(close_to(by_student[1], 35.0 / 3));
course_averages(grades, STUDENTS, by_course);
assert(close_to(by_course[0], 13.75));
best_students(grades, STUDENTS, best);
assert(best[0] == 2);
transpose(grades, result);
assert(result[2][1] == 8);
assert(is_transpose(grades, result));
result[0][0] = 0;
assert(!is_transpose(grades, result));
printf("Todas las pruebas pasaron\n");
return 0;
}// Laboratorio, parte 1: contar cuántas veces aparece una palabra en un texto.
//
// Escriban count_word usando strstr. Borren las líneas (void): solo están para que el
// archivo compile sin advertencias mientras tanto.
//
// En main hay algunas pruebas. Agreguen más con assert: casos normales y bordes.
//
// Pruébenlo con make test-lab.
#include <assert.h>
#include <stdio.h>
#include <string.h>
// Devuelve cuántas veces aparece word dentro de text, según estas reglas:
// - Se compara byte por byte: 'S' y 's' son distintos.
// - No hace falta que sea una palabra completa: "sol" aparece una vez en "girasol".
// - Las apariciones no se pisan: al encontrar una, se sigue buscando después de ella.
// "ana" aparece una vez en "banana", y "aa" aparece 2 veces en "aaaaa".
// - Si word es la cadena vacía, devuelve 0.
// Basta buscar desde &text[i], como explican los apuntes: no hace falta declarar punteros.
int count_word(const char text[], const char word[]) {
(void) text;
(void) word;
return 0;
}
int main(void) {
assert(count_word("sol y luna", "sol") == 1);
assert(count_word("sol y sol", "sol") == 2);
assert(count_word("sol y luna", "mar") == 0);
printf("Todas las pruebas pasaron\n");
return 0;
}Compilación
Cómo se compilan y ejecutan los ejemplos.
# Semana 11: biblioteca de cadenas y matrices. Se usa dentro del entorno del curso.
#
# make compila los programas de la clase en build/
# make test corre las pruebas de los ejemplos y de los apuntes
# make test-lab prueba su laboratorio (lab/*.c); 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.
#
# check_lab.c es parte de las pruebas: llama a las funciones de words.c y grades.c.
# Para enlazarlos, el main de cada archivo se compila con otro nombre.
CC = gcc
CFLAGS = -Wall -Wextra -std=c17 -g -fsanitize=address,undefined
BUILD = build
PROGRAMS = lengths compare strncpy_bytes safe_copy append find_text matrix_layout \
grades_table matrix_param names matrix_add
.PHONY: all lab test test-lab clean
all: $(PROGRAMS:%=$(BUILD)/%)
$(BUILD)/%: %.c | $(BUILD)
$(CC) $(CFLAGS) $< -o $@
$(BUILD) $(BUILD)/lab $(BUILD)/solution:
mkdir -p $@
$(BUILD)/lab/% : lab/%.c | $(BUILD)/lab
$(CC) $(CFLAGS) $< -o $@
$(BUILD)/solution/% : solution/%.c | $(BUILD)/solution
$(CC) $(CFLAGS) $< -o $@
$(BUILD)/lab/check_lab: check_lab.c lab/words.c lab/grades.c | $(BUILD)/lab
$(CC) $(CFLAGS) -Dmain=words_main -c lab/words.c -o $(BUILD)/lab/words.o
$(CC) $(CFLAGS) -Dmain=grades_main -c lab/grades.c -o $(BUILD)/lab/grades.o
$(CC) $(CFLAGS) check_lab.c $(BUILD)/lab/words.o $(BUILD)/lab/grades.o -o $@
$(BUILD)/solution/check_lab: check_lab.c solution/words.c solution/grades.c | $(BUILD)/solution
$(CC) $(CFLAGS) -Dmain=words_main -c solution/words.c -o $(BUILD)/solution/words.o
$(CC) $(CFLAGS) -Dmain=grades_main -c solution/grades.c -o $(BUILD)/solution/grades.o
$(CC) $(CFLAGS) check_lab.c $(BUILD)/solution/words.o $(BUILD)/solution/grades.o -o $@
LAB_FILES = words grades check_lab
lab: $(LAB_FILES:%=$(BUILD)/lab/%)
# Si existe solution/, también se comprueba la solución del laboratorio.
test: all
@if [ -d solution ]; then $(MAKE) --no-print-directory \
$(LAB_FILES:%=$(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__