Nei titoli e nei testi troverete qualche rimando cinematografico (ebbene si, sono un cinefilo). Se non vi interessano fate finta di non vederli, già che non sono fondamentali per la comprensione dei post...

Di questo blog ho mandato avanti, fino a Settembre 2018, anche una versione in Spagnolo. Potete trovarla su El arte de la programación en C. Buona lettura.

domenica 19 gennaio 2014

Syslog Royale
come scrivere un Syslog in C - pt.1

Quando si scrive Software bisogna sempre cercare di essere professionali, qualunque sia l'uso del Software che stiamo scrivendo. Ad esempio non ci si può dimenticare di prevedere un buon sistema di log (ci sono, poi, alcuni professionisti della programmazione che non sanno neanche cosa è un buon sistema di log... ma questa è un altra storia).

Per aggiungere un log al nostro Software possiamo aiutarci col sistema operativo, ad esempio in UNIX e Linux abbiamo Syslog che è molto flessibile e ci semplifica la vita. Nel caso di non potere (o non volere) usare i mezzi forniti dal OS si può pensare di scriversi un proprio sistema di log, come vedremo nel seguito di questo post. Cercheremo di emulare, più o meno, sintassi e funzionamento di Syslog, cercando di realizzare un prodotto semplice ma di qualità, cioè un prodotto professionale, perché noi cerchiamo sempre di essere professionali, magari senza arrivare agli eccessi di professionalità di quello del titolo.
Mi chiamo Log, James Log
Sia perché non voglio fare un post troppo lungo (diventerebbe noioso), sia per seguire una specie di approccio specifica+implementazione (adattato a un C-Blog), ho diviso il post in due puntate: nella prima descriverò, a mo' di specifica funzionale, l'header file (mylog.h) e un esempio di uso (main.c). Nella seconda puntata descriverò la vera e propria implementazione.

Vediamo l'header file, mylog.h:
// livelli di mylog
#define MYLOG_NOLOG   -1 // nessun messaggio di log
#define MYLOG_ERROR   0  // condizione di errore
#define MYLOG_WARNING 1  // condizione di warning
#define MYLOG_DEBUG   2  // messaggi di debug

// prototipi globali
void myOpenLog(const char *fname, int level);
void myLog(int level, const char *format, ...);
void myCloseLog();
Semplice e auto-esplicativo, no? Il nostro prodotto è formato da tre funzioni canoniche (apri, usa, chiudi), e lavora a livelli di gravità (per il momento quattro). Quasi inutile spiegare che, settando un livello, nel logfile appariranno tutti i messaggi di uguale o maggiore gravità del livello selezionato. Il livello NOLOG, poi, è in pratica un flag di disabilitazione che ci permette di avviare l'applicazione senza logfile, per alleggerire l'esecuzione: è scontato che deve essere una disabilitazione vera, cioè la CPU e il sistema di I/O devono veramente lavorare meno senza log attivato.

Vediamo ora l'esempio d'uso, main.c:
#include <stdio.h>
#include <stdlib.h>
#include "mylog.h"

int main(int argc, char* argv[])
{
    // test argomenti
    if (argc != 3) {
        printf("mylog: wrong arguments counts\n");
        printf("usage: mylog logfile loglevel [e.g.: mylog log.txt 1]\n");
        return EXIT_FAILURE;
    }

    // open log: set logfname e loglevel (0=error/1=warning/2=debug; -1=nolog)
    myOpenLog(argv[1], atoi(argv[2]));

    // test mylog()
    myLog(MYLOG_ERROR, "questo è un msg di tipo %d (il livello impostato è %d)",
        MYLOG_ERROR, atoi(argv[2]));
    myLog(MYLOG_WARNING, "questo è un msg di tipo %d (il livello impostato è %d)",
        MYLOG_WARNING, atoi(argv[2]));
    myLog(MYLOG_DEBUG, "questo è un msg di tipo %d (il livello impostato è %d)",
       MYLOG_DEBUG, atoi(argv[2]));

    // close log
    myCloseLog();

    // exit
    return EXIT_SUCCESS;
}
Semplice, no? Eseguo un test argomenti con esempio di lancio (usage...), e poi apro, uso, chiudo, esco. Usando una linea di comando del tipo "mylog log.log 1" quale sarà il risultato? Verrà creato un file "log.log" che conterrà le seguenti due linee:
2014-01-18 18:39:33.890271 - questo è un msg di tipo 0 (il livello impostato è 1)
2014-01-18 18:39:33.890407 - questo è un msg di tipo 1 (il livello impostato è 1)
Aggiungiamo dettagli alla specifica: successivi lanci dell'applicazione devono appendere linee al logfile, perché una dote fondamentale di un log è mantenere informazioni dell'accaduto (e se ogni volta resettiamo il file le perdiamo). E se vogliamo ripartire da zero possiamo usare un altro filename oppure cancellare il vecchio logfile prima di eseguire. Un altro dettaglio che si nota è che ci servono, oltre alle nostre scritte di traccia, anche delle informazioni orarie, magari molto precise (nell'esempio ci sono i microsecondi: nella prossima puntata spiegherò il perché).

Per oggi abbiamo finito. I più volenterosi potranno, nell'attesa della seconda parte, scrivere una propria implementazione, e poi confrontarla con la mia, ma la mia sarà sicuramente meglio... (si allontana sghignazzando).

Ciao e al prossimo post!

domenica 1 dicembre 2013

Bitwise operations: Ep.II - L'attacco degli shift
come usare gli operatori Bitwise in C - pt.2

Ok, come promesso qui, torniamo sull'argomento bitwise per descrivere alcuni risultati inaspettati e fornire alcuni semplici esempi pratici.

Cominciamo dalle dolenti note: come anticipato nel post precedente, le moltiplicazioni e divisioni con shift, in base alla dimensione del tipo del operando e alla presenza o meno del bit di segno, possono dare risultati inaspettati. Vediamo un esempio:
void main()
{
    // 1) SHIFT a sinistra con unsigned int
    unsigned int a, b;
    a = 74;           // 0 0 1 0 0 1 0 1 0
    b = a << 2;       // 1 0 0 1 0 1 0 0 0 risultato b=296
    printf("var = %d; var << 2 = %d\n", a, b);

    // 2) SHIFT a sinistra con unsigned char
    unsigned char c, d;
    c = 74;           // 0 1 0 0 1 0 1 0
    d = c << 2;       // 0 0 1 0 1 0 0 0 risultato d=40
    printf("var = %d; var << 2 = %d\n", c, d);

    // 3) SHIFT a destra con signed char (o int)
    char e, f;
    e = -4;           // 1 1 1 1 1 1 0 0
    f = e >> 2;       // 1 1 1 1 1 1 1 1 risultato f=-1
    printf("var = %d: var >> 2 = %d\n", e, f);
    // N.B.: senza estensione di segno sarebbe:
    // e = -4;        // 1 1 1 1 1 1 0 0
    // f = e >> 2;    // 0 0 1 1 1 1 1 1 risultato f=63
}
Il caso 1 è, evidentemente il caso funzionante: un int è molto più grande degli 8 bit usati per rappresentare il numero di partenza (74), per cui lo shift non perde nessun 1 sulla sinistra (è questo il possibile problema) e il risultato è corretto (74x4=296). Se usiamo però (caso 2) un char (8 bit) durante lo shift perdiamo un 1 e il risultato va in overflow (74x4=40 ??). Quindi attenzione!

Il caso 3, poi, è ancora più subdolo: facendo operazioni con segno (nei casi 1 e 2 ho usato variabili unsigned proprio in preparazione al punto 3) e usando valori negativi, possono succedere cose strane: per la rappresentazione stessa dei numeri negativi in binario (complemento a 2) il bit più a sinistra (MSB) è il bit di segno, e, in questo caso l'operazione di shift è machine-dependent: in base al tipo di CPU possiamo disporre o no dell'estensione di segno (di default, ad esempio, su macchine Intel), per cui lo shift dell'esempio può dare il risultato aspettato (-4/4=-1) o un risultato completamente diverso (-4/4=63 ??). Di nuovo: attenzione!

E adesso è il momento di mostrare alcuni esempi pratici di uso di quanto esposto, che altrimenti, sarebbe know-how fine a se stesso: vediamo come leggere lo stato dei singoli bit di una word usando una maschera:
void main()
{
    // uso di una maschera per leggere i bit di una word
    unsigned char mask = 1;     // 0 0 0 0 0 0 0 1
    unsigned char word = 74;    // 0 1 0 0 1 0 1 0

    // loop di lettura
    int i;
    for (i = 0; i < 8; i++)
        printf("il bit %d della word è %s\n",
                i, (word & mask<<i) ? "ON" : "OFF");
}
semplice no? E la stessa operazione si può fare con una macro:
#define INPUT(w, i)    (w & 0x01<<i)

void main()
{
    // uso di una macro per leggere i bit di una word
    unsigned char i_word = 74;    // 0 1 0 0 1 0 1 0

    // loop di lettura
    int i;
    for (i = 0; i < 8; i++)
        printf("il bit %d della word è %s\n",
                i, INPUT(i_word, i) ? "ON" : "OFF");
}
E poi, visto che questo è un blog di stile, vediamo un modo con una una buona estetica per leggere degli input di un dispositivo, per esempio i fine corsa di un sistema elettromeccanico che dobbiamo controllare col nostro amato C:
#define INPUT_FC1    (in_word & 0x01<<0)
#define INPUT_FC2    (in_word & 0x01<<1)

void main()
{
    // uso di una define per ogni bit da leggere di una word
    unsigned char in_word = 74;    // 0 1 0 0 1 0 1 0

    // lettura
    printf("il bit FC1 della word è %s\n", INPUT_FC1 ? "ON" : "OFF");
    printf("il bit FC2 della word è %s\n", INPUT_FC2 ? "ON" : "OFF");
}
Ecco, l'esempio appena mostrato indica una maniera, semplice ed elegante, per descrivere degli input (usando dei mnemonici auto-esplicativi) che può risultare utile per scrivere del Software di controllo di dispositivi Hardware, facile da leggere e da manutenere.

Facile da leggere e da manutenere: questa l'ho già sentita... ah si: è come dovrebbe essere tutto il S/W che scrive un Analista Programmatore (e peccato che molti analisti programmatori con la minuscola si scordano di questo imperativo quando si siedono davanti a un computer...).

Ciao e al prossimo post!