Printf
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
1identifica il file descriptor in cui scrivere. In questo caso1identifica lo stream di output, noto come STANDARD OUTPUT ostdout. -
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:
-
alldeve compilare
src/printf.cesrc/main.ce produrre il programma./printf. -
cleanelimina 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.