← Torna al progetto

Printf

Variadic functions C Step 1 di 13

Project setup

Iniziamo impostando la seguente struttura al progetto

Makefile         // per compilare
src/printf.h     // contiene i prototipi
src/printf.c     // contiene le funzioni, senza main()
src/main.c       // il programma con il main()

Da notare che il file src/printf.c non deve contenere la funzionemain(). L'idea infatti è quella di sviluppare una libreria, non un programma. Il src/main.c andrà a testare la printf e dunque dovrà contenere un main.

La prima funzione

La funzione che andremo a costruire nel corso del progetto si chiama esa_printf, e ha il seguente prototipo, da mettere in printf.h

int esa_printf(const char *format, ...);

I tre puntini ... dicono al compilatore che dopo format possono esserci altri argomenti. Quanti? Un numero arbitrario. Queste funzioni, quelle che prendono un numero arbitrario di argomenti, si dicono variadic e saranno trattate nel prossimo step.

L'obiettivo di questo step è implementare una prima versione di esa_printf che non converte niente: prende la format string e la copia sullo standard output, un carattere alla volta, % compresi, senza nessuna particolare interpretazione.

Scrivere con write

L'unica primitiva che possiamo utilizzare per scrivere sul terminale è la primitiva write, definita dalla libreria <unistd.h>. Tale primitiva può essere utilizzata nel seguente modo

#include <unistd.h>

int main(void) {
    /* stampa un carattere */
    char a = 'a';
    write(1, &a, 1);

    /* stampa una stringa */
    char hello = "Hello World!\n"
    write(1, hello, 14);
}

Osserviamo dalla man page la signature della write

NAME
     pwrite, write —— write on a file

SYNOPSIS
     #include <unistd.h>

     ssize_t write(int fildes, const void *buf, size_t nbyte);

DESCRIPTION
     The write() function shall attempt to write nbyte bytes from the buffer
     pointed to by buf to the file associated with the open file descriptor, fildes.

Questo significa che i tre parametri di una chiamata a write write(1, hello, 14) devono essere interpretati nel seguente modo:

  • Il primo parametro 1 identifica il file descriptor in cui scrivere. In questo caso 1 identifica lo stream di output, noto come STANDARD OUTPUT o stdout.

  • Il secondo parametro identifica l'indirizzo della memoria che si vuole stampare.

  • Il terzo parametro identifica la quantità di byte che si vuole stampare.

In ultima analisi questo progetto vuole rispondere alla seguente domanda: come possiamo implementare la funzionalità offerta da printf, se abbiamo a disposizione solamente una write e il supporto per le funzioni variadic?

Makefile

L'obiettivo del Makefile sarà produrre il binario finale. Nello specifico il make deve supportare i seguenti target:

  • all

    deve compilare src/printf.c e src/main.c e produrre il programma ./printf.

  • clean

    elimina il binario prodotto dalla compilazione. Deve funzionare anche quando il binario non è presente.

Da notare che la compilazione deve rispettare i seguenti flags

-Wall -Wextra -Werror -pedantic -std=c99

I binari prodotti dalla compilazione non devono essere inseriti nel sistema di versioning.

Risultato

Alla fine dobbiamo ottenere la seguente situazione

$ make
gcc -Wall -Wextra -pedantic -std=c99 -ggdb src/printf.c src/main.c -o printf
$ ./printf
Hello, world!

Inizia il progetto per avere un repository e poter consegnare gli step.

Congratulazioni, hai completato il progetto Printf

Tutti i 13 step sono superati.