En esta página

42 Piscine — Rush 00 (explicado desde cero)

Vas a hacer que un programa de C dibuje un rectángulo en la pantalla:

o---o
|   |
o---o

Ese dibujo lo produce rush(5, 3): 5 caracteres de ancho y 3 de alto. Este documento explica cada línea de los tres archivos, asumiendo que llevas un día programando. Si algo no se entiende, es culpa de este README, no tuya.

Hecho con amor para Laura, por Bogdan.

¿Acabas de empezar? CONCEPTOS.md explica, por separado, qué es una variable, una función, un return temprano, un bucle, una matriz, y qué hace gcc con sus flags. Este README asume que ya leíste eso.


Qué tienes que entregar

DatoValor
Carpetaex00/
Archivosmain.c, ft_putchar.c, rush00.c
Funciones de librería permitidassolo write
Función obligatoriavoid rush(int x, int y)

Tres archivos, no más. El enunciado dice que ft_putchar.c debe contener únicamente la función ft_putchar, así que no agregues nada ahí.


Modelo mental: qué hace un compilador

Antes de leer código, necesitas una imagen mental. Programar en C es escribir un archivo de texto (.c) con instrucciones que una persona puede leer. El ordenador no ejecuta ese texto: primero hay que traducirlo.

  rush00.c  ──┐
  main.c    ──┼──► gcc ──► rush00  (programa ejecutable)
  ft_putchar.c┘

Tres ideas que van a aparecer todo el tiempo:

  • Compilar: traducir cada .c a instrucciones de máquina.
  • Enlazar (link): unir las piezas ya compiladas en un solo ejecutable.
  • Ejecutar: correr ese ejecutable.

Si te olvidas de un archivo en el comando de gcc, el enlazador se queja de que no encuentra una función. Es el error más común y lo vas a ver más abajo.


El problema: filas, columnas y bordes

rush recibe dos números enteros:

  • x = ancho, cuántos caracteres entran en una fila.
  • y = alto, cuántas filas hay.

Si x = 5 e y = 3, la salida es una grilla de 5 columnas por 3 filas:

columna:  0   1   2   3   4
         ┌───┬───┬───┬───┬───┐
fila 0   │ o │ - │ - │ - │ o │
         ├───┼───┼───┼───┼───┤
fila 1   │ | │   │   │   │ | │
         ├───┼───┼───┼───┼───┤
fila 2   │ o │ - │ - │ - │ o │
         └───┴───┴───┴───┴───┘

Las reglas son tres:

PosiciónCarácter
Esquina (primera/última fila y primera/última columna)o
Borde de arriba o de abajo (sin ser esquina)-
Borde izquierdo o derecho (sin ser esquina)|
Todo lo de adentroespacio ' '

Y algo que confunde a todos el primer día: los índices empiezan en 0. Si hay 5 columnas, sus índices son 0, 1, 2, 3, 4. La última columna es x - 1, no x. Por eso en el código vas a ver width - 1 todo el tiempo.


Archivo 1: ft_putchar.c

#include <unistd.h>

/*
** Escribe un solo caracter en la salida estandar (file descriptor 1).
** Es la unica funcion permitida por el enunciado para mostrar texto.
*/
void    ft_putchar(char c)
{
    write(1, &c, 1);
}

#include <unistd.h>

Una línea que empieza con # no es C normal: es una orden para el preprocesador, un paso previo que copia y pega cosas antes de compilar.

unistd.h es un archivo del sistema ("Unix standard") que contiene la declaración de write. Es como el índice de un libro: le dice al compilador "existe una función llamada write que recibe estos parámetros". Sin esa línea, el compilador no sabe qué es write y con -Werror el programa ni compila.

void ft_putchar(char c)

Esta línea se lee así, de izquierda a derecha:

ParteSignificado
voidla función no devuelve ningún valor
ft_putcharel nombre (la ft_ es convención de 42)
(char c)recibe un parámetro llamado c, de tipo char

char es el tipo de dato más chico: guarda un carácter. Cuando main llama a ft_putchar('o'), la función recibe una copia de ese carácter dentro de una variable local llamada c. Esa variable vive en una cajita de memoria que existe solo mientras la función se está ejecutando.

write(1, &c, 1);

write es una función del sistema operativo que escribe bytes en un descriptor de archivo. Tiene tres parámetros:

ParámetroQué significa en este caso
1dónde escribir. 1 = salida estándar (la pantalla / la terminal)
&cqué escribir: la dirección de memoria donde está el carácter
1cuántos bytes escribir: uno solo

Dos detalles que valen oro:

  1. & significa "la dirección de". write no recibe el valor del carácter, recibe la ubicación de la cajita donde está guardado. Por eso se escribe &c y no c. Si pasas c, el compilador te va a marcar error de tipos (y con razón).
  2. write devuelve cuántos bytes pudo escribir. Aquí ese número no nos interesa y se descarta. Eso es normal: no todo lo que devuelve una función se usa.

Otros descriptores que existen: 0 es la entrada (teclado), 2 es la salida de errores. 1 es la de siempre.

Nota de estilo: la indentación es con tabs, no con espacios. Es la Norm de 42 y el corrector lo revisa.


Archivo 2: rush00.c

Este archivo tiene una función pública (rush) y dos funciones auxiliares. Lo leemos de abajo hacia arriba: primero las piezas chicas, después el que las usa.

La declaración de ft_putchar

void    ft_putchar(char c);

Esto es un prototipo: la firma de una función que está en otro archivo.

¿Por qué hace falta? Porque el compilador procesa rush00.c sin ver ft_putchar.c. El prototipo le avisa "tranquilo, esta función existe en algún lado, y recibe un char". Gracias a eso puede verificar que la llames bien. Si lo borras, el compilador asume que existe y te tira un warning de implicit declaration, que con -Werror se convierte en error.

Fíjate que termina en ;: es una declaración, no una definición. No lleva cuerpo, no ejecuta nada.

static void print_horizontal_edge(int width)
{
    int column;

    column = 0;
    while (column < width)
    {
        if (column == 0 || column == width - 1)
            ft_putchar('o');
        else
            ft_putchar('-');
        column++;
    }
    ft_putchar('\n');
}

¿Qué significa static?

Una función static solo existe dentro de este archivo. Es una función privada. ¿Por qué querrías eso?

  • Comunica la intención: "esto es un ayudante interno, no lo uses desde afuera".
  • Evita choques de nombres si otro archivo define una función igual.

No es obligatorio para aprobar, pero es buena práctica y es el equivalente en C a marcar un método como privado.

Paso a paso

int column; — declaramos una variable de tipo int (entero). La declaramos arriba del bloque porque el estilo de 42 (basado en C89) pide todas las declaraciones al principio.

column = 0; — le damos un valor inicial. Ojo: la declaración por sí sola no inicializa nada. Si te olvidas de esta línea, column contiene basura y el while puede comportarse de forma impredecible.

while (column < width) — un bucle. La condición se evalúa antes de cada vuelta:

"Mientras column sea menor que width, repite todo lo que está entre llaves."

Con width = 5, el cuerpo se ejecuta con column = 0, 1, 2, 3, 4. Cuando column llega a 5, la condición 5 < 5 es falsa y el bucle termina.

if (column == 0 || column == width - 1) — una decisión.

  • == compara (devuelve verdadero o falso). No es lo mismo que =, que asigna. Confundirlos es el error número uno de todo principiante.
  • || significa o. La condición es verdadera si el lado izquierdo o el derecho lo son.
  • column == 0 → ¿es la primera columna?
  • column == width - 1 → ¿es la última columna? (width - 1 es una resta de enteros: con width = 5 da 4.)

Si es primera o última columna escribimos 'o'; si no, '-'.

'o' (comillas simples) es un carácter. "o" (comillas dobles) es un string, que en C ocupa dos bytes (la o y un terminador invisible). ft_putchar espera un char, así que siempre comillas simples.

column++; — column++ es la forma corta de column = column + 1;. Está dentro del while, al final del cuerpo. Es lo que hace avanzar el bucle. Si te olvidas de esta línea, el programa nunca termina: column queda en 0 para siempre y la condición siempre es verdadera. El enunciado prohíbe explícitamente el bucle infinito, así que revisa esto.

ft_putchar('\n'); — después de completar la fila, escribimos un salto de línea. '\n' es un solo carácter (el salto de línea), no dos. Sin esto, todas las filas saldrían pegadas en la misma línea:

o---o|   |o---o

Traza de print_horizontal_edge(5)

Vueltacolumncolumn == 0column == 4¿Qué imprime?Salida acumulada
10síno se evalúaoo
21nono-o-
32nono-o--
43nono-o---
54nosíoo---o
—5el bucle termina (5 < 5 es falso)\no---o\n
static void print_vertical_edge(int width)
{
    int column;

    column = 0;
    while (column < width)
    {
        if (column == 0 || column == width - 1)
            ft_putchar('|');
        else
            ft_putchar(' ');
        column++;
    }
    ft_putchar('\n');
}

Es la misma estructura que la anterior. Solo cambian dos cosas: los bordes son '|' y el relleno es un espacio ' ' (que también es un carácter, y para el ordenador es tan real como una letra).

Con width = 5 imprime: |, espacio, espacio, espacio, |, salto de línea. Eso es exactamente | |.

rush: el director de orquesta

void    rush(int x, int y)
{
    int row;

    if (x <= 0 || y <= 0)
        return ;
    row = 0;
    while (row < y)
    {
        if (row == 0 || row == y - 1)
            print_horizontal_edge(x);
        else
            print_vertical_edge(x);
        row++;
    }
}

void rush(int x, int y) — la función que pide el enunciado, con esa firma exacta. Recibe ancho (x) y alto (y).

if (x <= 0 || y <= 0) + return ; — esto se llama guard clause (cláusula de guardia): resolver el caso inválido primero y salir.

  • <= significa "menor o igual". Cubre el cero y los negativos con una sola comparación.
  • return ; sin valor corta la función en el acto. Como rush es void, no hay nada que devolver.
  • Sin esta guardia, con y = 0 el while no entraría (bien), pero con y = -3 la lógica de "primera o última fila" se vuelve absurda y podrías entrar en un bucle raro. Además, el enunciado aclara que no se debe fallar con negativos. Aquí elegimos no dibujar nada; está permitido siempre que lo sepas explicar en la defensa.

row = 0; y while (row < y) — recorremos las filas, de la 0 a la y - 1. Si y = 3, son las filas 0, 1, 2.

if (row == 0 || row == y - 1) — la pregunta clave de todo el ejercicio:

¿Esta fila es la primera o la última?

  • Si sí → es un borde horizontal → print_horizontal_edge(x).
  • Si no → es una fila del medio → print_vertical_edge(x).

Fíjate que rush no sabe cómo se dibuja un borde. No usa 'o' ni '-'. Solo decide qué fila toca y delega el cómo. Esa separación es la que hace el código fácil de leer.

row++; — avanzamos a la siguiente fila. Mismo aviso que antes: sin esto, bucle infinito.


Traza completa: qué pasa con rush(5, 3)

Vueltarowrow == 0row == 2Función llamadaQué imprime
10síno se evalúaprint_horizontal_edge(5)o---o\n
21nonoprint_vertical_edge(5)| |\n
32nosíprint_horizontal_edge(5)o---o\n
—3termina (3 < 3 es falso)

Resultado en pantalla:

o---o
|   |
o---o

Nada mágico: tres llamadas a funciones auxiliares, una por fila.


Casos límite

El enunciado pide admitir cualquier entero, incluidos cero y negativos.

CasoQué pasaPor qué funciona sola
x = 0no imprime nadala guardia corta antes de dibujar
y = 0no imprime nadaidem
x o y negativosno imprime nada<= 0 los atrapa a todos
x = 1, y = 5una columna: o, |, |, |, ocon width = 1, width - 1 = 0, así que la misma columna es primera y última
y = 1una sola fila: o---oen la vuelta row = 0 se cumple row == 0 y row == y - 1; se dibuja un solo borde
x = 2oo arriba y abajo, || al mediono queda lugar para - ni para espacios; es correcto

El caso x = 1 es el más lindo: no necesita ningún if extra. La condición column == 0 || column == width - 1 ya contempla que ambos lados sean el mismo índice. Por eso rush(1, 1) imprime un único o.


Archivo 3: main.c

/* Prototipo de la funcion que vive en rush00.c. */
void    rush(int x, int y);

int main(void)
{
    rush(5, 3);
    return (0);
}

int main(void) — main es el punto de entrada: por aquí arranca siempre el programa, lo llame quien lo llame. Devuelve un int al sistema operativo: 0 significa "terminó bien". Cualquier otro número indica error.

void entre paréntesis significa "no recibe argumentos" (no confundir con el void de ft_putchar, que ahí significaba "no devuelve nada"). Sí, C reusa la misma palabra para dos cosas distintas. Bienvenido.

rush(5, 3); — una llamada a función. 5 y 3 son los argumentos; x e y son los parámetros que los reciben. Es el ejemplo del enunciado.

Durante la defensa, el corrector va a reemplazar estos números por otros (incluso negativos) para ver si tu función aguanta. Por eso main.c es tan corto: no es el ejercicio, es solo el botón de arranque.


Compilar y ejecutar

cd ex00
gcc -Wall -Wextra -Werror main.c ft_putchar.c rush00.c -o rush00
./rush00

Qué hace cada parte

ParteSignificado
gccel compilador de C
main.c ft_putchar.c rush00.clos tres archivos; si falta uno, el enlazador falla
-Wallmuestra los warnings más comunes
-Wextramuestra warnings adicionales
-Werrorconvierte cada warning en error: si algo está mal, no compila
-o rush00nombre del ejecutable de salida

-Werror parece cruel, y lo es a propósito: 42 no acepta código que compile "con advertencias". Mejor que el error te lo diga el compilador y no el corrector.

¿Qué es gcc y por qué esos flags? Está explicado a fondo, con las cuatro etapas de la compilación y un flag por flag, en CONCEPTOS.md.

Ejecutar

./rush00

El ./ significa "busca el ejecutable en el directorio actual". No es decoración: por seguridad, el directorio actual no está en el PATH, así que escribir solo rush00 no lo encuentra.

Deberías ver:

o---o
|   |
o---o

Probar otros tamaños

Editá el main.c, cambia los números y vuelve a compilar:

rush(4, 4);
o--o
|  |
|  |
o--o
rush(1, 5);
o
|
|
|
o

No hace falta adivinar: compila, mira la salida y compárala con el enunciado.


Errores típicos del primer día

Mensaje / síntomaCausaSolución
implicit declaration of function 'write'falta #include <unistd.h>agrégalo arriba
implicit declaration of function 'ft_putchar'falta el prototipo en rush00.c o main.cagrega void ft_putchar(char c);
undefined reference to 'ft_putchar'no incluiste ft_putchar.c en el comando de gccpasa los tres archivos
expected ';' before ...falta un punto y comabusca la línea anterior a la que marca
El programa no termina nuncate olvidaste column++; o row++;agrégalo dentro del while
Todo sale en una sola líneate olvidaste ft_putchar('\n');agrégalo al final de cada fila
warning: unused parameterdeclaraste un parámetro que no usasúsalo o quita el nombre
No aparece nada y antes andabax o y en 0/negativoses el comportamiento esperado
permission denied al ejecutarfalta el ././rush00

Por qué el código está ordenado así (SOLID)

SOLID es un conjunto de principios pensados para diseño orientado a objetos: hablan de clases, interfaces y herencia. En C de piscina no hay clases, así que no aplica entero. Lo que sí se traslada, y es lo que usamos aquí:

PrincipioCómo se ve en este código
S — Single Responsibilityprint_horizontal_edge dibuja bordes; print_vertical_edge dibuja el medio; rush decide el orden. Cada una hace una cosa.
O — Open/ClosedPara otro dibujo (por ejemplo un triángulo) agregas funciones nuevas; no reescribes rush.
L / I / DNo tienen equivalente útil en un ejercicio de 40 líneas. Forzarlos sería sobreingeniería.

La regla práctica que importa: una función, una tarea, un nombre honesto. Si una función necesita la palabra "y" para describirse ("dibuja el borde y decide la fila"), son dos funciones.

Lo que no hay que hacer aquí: crear structs, punteros a función o un sistema de "renderers" para dibujar un rectángulo. La Norm de 42 no premia la complejidad: premia que se entienda.


Glosario

TérminoSignificado
compiladorprograma que traduce C a instrucciones de máquina (gcc)
enlazador (linker)une los archivos ya compilados en un ejecutable
preprocesadorpaso previo que procesa los #include
prototipola firma de una función, sin su cuerpo
parámetro / argumentola variable que recibe la función / el valor que le pasas
bloquetodo lo que está entre { y }
iteraciónuna vuelta de un bucle
descriptor de archivonúmero que identifica dónde leer/escribir (1 = pantalla)
guard clauseresolver el caso inválido al principio y salir con return
índiceposición de un elemento, empezando en 0
bucle infinitoun while cuya condición nunca se vuelve falsa
Normreglas de estilo de 42 (tabs, nombres, cantidad de líneas)

Siguiente paso

Compila, cambia los números del main.c y predice la salida antes de ejecutar. Si aciertas los cinco ejemplos del enunciado, entendiste el ejercicio. Si no, vuelve a la traza de rush(5, 3): ahí está todo.

Después del rush: el módulo C 00

El rectángulo es el Rush 00, pero el módulo C 00 de la piscina tiene nueve ejercicios. Cada uno tiene su apartado propio, con el código explicado línea por línea, la salida exacta y los errores típicos:

EjercicioQué practica
00 · ft_putcharwrite, direcciones y char
01 · ft_print_alphabetun bucle y la consecutividad de ASCII
02 · ft_print_reverse_alphabetel mismo bucle al revés
03 · ft_print_numbersconvertir número en carácter
04 · ft_is_negativedecisiones y el caso del cero
05 · ft_print_combtres bucles y separadores
06 · ft_print_comb2dos cifras con / y %
07 · ft_putnbrrecursión e INT_MIN
08 · ft_print_combnarrays, topes y recursión

Los nueve se comprueban de una vez con:

./scripts/check-exercises.sh

Compila cada carpeta con -Wall -Wextra -Werror, ejecuta el resultado con un main temporal y compara la salida byte a byte. Si algo falla, imprime el primer byte distinto.