Newsgroup: it.comp.lang.c
Titolo: "Variabili generiche" - Parte II

Ok, ho contribuito a una grande confusione e per questo è venuta fuori una
discussione interessante ma tronca.
Vi spiego meglio il problema. Avete capito che si tratta di un sistema limitato
nelle risorse, anche se avevo sbagliato i numeri.
Flash: 2Mbit=256KByte
RAM: 6KByte
Interi: 16 bit
Compilatore: C89
La mia applicazione embedded deve eseguire certi compiti, in base a delle
impostazioni che vengono lette, all'avvio, da una EEPROM esterna. L'utente
può interagire, durante il funzionamento, mediante display e tasti, e
modificare alcune impostazioni che verranno salvate sempre in EEPROM così
da essere ripescate alla successiva riaccensione.
Oltre alle impostazioni (parametri di lettura/scrittura) ci sono delle
variabili di stato (parametri di sola lettura).
Chiamiamo, quindi, genericamente con parametro una di queste variabili
che può essere un unsigned char, unsigned int, unsigned long, signed char,
signed int, signed long. In realtà ci sarebbero anche parametri
rappresentati da sequenze di byte, quindi non tipati, ma diciamo che
posso trattarli in modo particolare essendo pochi.
Siccome i parametri sono tanti (diciamo un centinaio), vorrei creare un
insieme di funzioni generiche e valide per tutti, indipendentemente dal
tipo. Queste funzioni consistono nella visualizzazione della variabile
(come intero) e nella sua modifica (incremento/decremento). Tutto quanto
impostato con una serie di menu ad albero: menu principale, si sceglie
un gruppo e si entra in un sotto-menu, ecc. ecc. fino ad arrivare ai
parametri veri e propri che è possibile leggere ed eventualmente modificare.
La struttura che mi rappresenta un parametro generico è ovviamente fissa
e costante, quindi avevo pensato di mantenerla in Flash, così da risparmiare
la RAM. La cosa importante è avere dei puntatori alla variabile che
contiene il valore attuale. Qualcosa del tipo (considerando la discussione
precedente):
   typedef enum {
     PARAMTYPE_UCHAR=0,
     PARAMTYPE_SCHAR,
     PARAMTYPE_UINT,
     PARAMTYPE_SINT,
     PARAMTYPE_ULONG,
     PARAMTYPE_SLONG,
   } ptype;
   typedef struct {
     ptype  type;
     union {
       unsigned char     *vptr_uchar;
       signed   char     *vptr_schar;
       unsigned int      *vptr_uint;
       signed   int      *vptr_sint;
       unsigned long     *vptr_ulong;
       signed   long     *vptr_slong;
     } value;
     void  *value;
   } param;
Fin qui tutto bene, ma siccome devo definire una variabile param di tipo const
(sì, posso ordinare al compilatore di lasciare le costanti in ROM piuttosto che
spostarle in RAM) dovrei fare qualcosa del genere:
   unsigned char  myuchar;
   const param  myparam_uchar = { PARAMTYPE_UCHAR, {&myuchar} };
Anche questo è a posto perché vptr_uchar è il primo membro della union. Per un
parametro di tipo signed char dovrei fare:
   signed char  myschar;
   const param  myparam_schar = { PARAMTYPE_SCHAR, {&myschar} };
...ma non va bene, perché il compilatore assegnerebbe &myschar (puntatore
ad un signed char) al primo membro della union, cioé a vptr_uchar (puntatore
a unsigned char). Forse mi darebbe un errore o un warning.
Ecco qual è il problema di avere una union costante e inizializzata. Ed ecco
perché, penso che la soluzione sia la seguente:
   typedef struct {
     ptype  type;
     void   *vptr;
   } param;
Ogni volta che devo accedere al valore, devo controllare type e fare un cast
appropriato. Per esempio, volendo visualizzare il valore numerico:
   switch(type) {
     case PARAMTYPE_UCHAR:
       printf( "%d", *(unsigned char *)myparam.ptr );
       break;
     case PARAMTYPE_SCHAR:
       printf( "%u", *(signed char *)myparam.ptr );
       break;
     case PARAMTYPE_UINT:
       printf( "%d", *(unsigned int *)myparam.ptr );
       break;
     case PARAMTYPE_SINT:
       printf( "%u", *(signed int *)myparam.ptr );
       break;
     case PARAMTYPE_ULONG:
       printf( "%ld", *(unsigned long *)myparam.ptr );
       break;
     case PARAMTYPE_SLONG:
       printf( "%lu", *(signed long *)myparam.ptr );
       break;
   }
Che ne dite? Rimane, poi, il problema di modificare questi valore, per esempio
incrementando e decrementando di una o più unità all'interno di un certo
range minimo-massimo. Ma magari ragioniamo su un problema alla volta.

 ha scritto nel messaggio
news:vxsxg.52237$zy5.1...@twister1.libero.it...

non serve = 0, il default è già così.

Alla fine mi sembra la scelta più semplice e giusta, ma in fondo
era stato già detto...
Comunque non hai indovinato nemmeno un formato...  :-)
E poi metti sempre '\n' con printf(), o almeno fflush(stdout).
Suppongo che lo fai dopo.
    switch(myparam.type) {
      case PARAMTYPE_UCHAR:
        printf( "%u", *(unsigned char *)myparam.vptr ); /* se gli unsigned
char NON stanno in int */
        printf( "%d", *(unsigned char *)myparam.vptr ); /* se gli unsigned
char stanno in int */
        break;
      case PARAMTYPE_SCHAR:
        printf( "%d", *(signed char *)myparam.vptr ); /* sempre int */
        break;
      case PARAMTYPE_UINT:
        printf( "%u", *(unsigned int *)myparam.vptr ); /* sempre uint */
        break;
      case PARAMTYPE_SINT:
        printf( "%d", *(signed int *)myparam.vptr ); /* sempre int */
        break;
      case PARAMTYPE_ULONG:
        printf( "%lu", *(unsigned long *)myparam.vptr ); /* sempre ulong */
        break;
      case PARAMTYPE_SLONG:
        printf( "%ld", *(signed long *)myparam.vptr ); /* sempre long */
        break;
    }
    printf("\n");
--
Giorgio Silvestri

 char NON stanno in int */
 >         printf( "%d", *(unsigned char *)myparam.vptr ); /* se gli unsigned
 > char stanno in int */
Beh, gli unsigned char sono a 8 bit, mentre gli int sono a 16 bit, quindi
dovrebbero starci. In questo caso %u e %d dovrebbero essere equivalenti,
o no?

Ok, a questo punto veniamo all'altro problema, quello della modifica
del "parametro generico". Il 99% di questi parametri numerici ha un
range di validità da un minimo ad un massimo. Diciamo che anche
questi sono numeri costanti per ogni parametro (almeno spero... non
li ho ancora controllati tutti). Quindi avevo pensato di inglobarli
nella mia const param.
   typedef struct {
     ptype type;
     void  *vptr;
     ...   min;
     ...   max;
   } param;
Ma che ci metto al posto dei puntini? Io attualmente ho risolto scegliendo
il peggio, cioé un long.
   typedef struct {
     ptype type;
     void  *vptr;
     long  min;
     long  max;
   } param;
Anche se facessi una union (ignorando il problema della inizializzazione con
il compilatore C89) comunque "sprecherei" lo spazio di un long.
L'unico problema è che non riuscirei a definire dei valori per max maggiori
di 2^31 (i long sono a 32bit). Ma diciamo che non dovrei avere mai casi di
questo tipo.
Quindi la funzione che mi incrementa un parametro generico dovrebbe essere
del tipo:
   void
   param_incr( param *p )
   {
     unsigned char uc;
     signed   char sc;
     unsigned int  ui;
     signed   int  si;
     unsigned long ul;
     signed   long sl;
     switch( p->ptype ) {
       case PARAMTYPE_UCHAR:
         uc = *(unsigned char *)p->vptr;
         if( uc++!=(unsigned char)p->max )
           *(unsigned char *) = uc;
         break;
       case PARAMTYPE_SCHAR:
         sc = *(signed char *)p->vptr;
         if( sc++!=(signed char)p->max )
           *(signed char *) = sc;
         break;
       case PARAMTYPE_UINT:
         ui = *(unsigned int *)p->vptr;
         if( ui++!=(unsigned int)p->max )
           *(unsigned int *) = ui;
         break;
       case PARAMTYPE_SINT:
         si = *(signed int *)p->vptr;
         if( si++!=(signed int)p->max )
           *(signed int *) = si;
         break;
       case PARAMTYPE_ULONG:
         ul = *(unsigned long *)p->vptr;
         if( ul++!=(unsigned long)p->max )
           *(unsigned long *) = ul;
         break;
       case PARAMTYPE_SLONG:
         sl = *(signed long *)p->vptr;
         if( sl++!=(signed long)p->max )
           *(signed long *) = sl;
         break;
     }
   }
Dovrebbe funzionare ma ho due problemi.
Come fare per generalizzare la funzione in modo che l'incremento non sia
di una unità ma di n, generico? Diciamo che n è per forza un unsigned char.
La routine precedente modifica la variabile direttamente in RAM, però non
è proprio quello che voglio ottenere. Durante la modifica del parametro,
fino a quando l'utente non conferma il nuovo valore, la variabile in RAM
*non* deve essere modificata, ma "conservata" in una variabile temporanea
che verrà usata solo per la visualizzazione. In altre parole, la routine
param_incr() dovrebbe tornare il nuovo valore del parametro *senza*
modificare il valore della variabile. Ma che tipo deve tornare la funzione
se non lo conosco? A questo punto, penso che l'unica soluzione sia quella
di creare tante funzioni, una per ogni tipo:
   unsigned char
   param_incr_uc( param *p )
   {
     unsigned char uc = *(unsigned char *)p->vptr;
     if( uc++==(unsigned char)p->max )
       return *(unsigned char *)p->vptr;
     else
       return uc;
   }
E così per tutte le altre funzioni per gli altri tipi.
La routine che deve incrementare il valore di p farà una cosa di questo tipo:
   void
   param_incr( param *p, unsigned char n )
   {
     /* Valori temporaneo, prima della conferma dell'utente. */
     char buf[12];
     static unsigned char uc_temp;
     static signed char   sc_temp;
     ...
     switch( p->ptype ) {
       case PARAMTYPE_UCHAR:
         uc_temp = param_incr_uc(p, n);  /* Routine che incrementa di n */
         sprintf( buf, "%u", uc_temp );
         break;
       case PARAMTYPE_SCHAR:
         sc_temp = param_incr_sc(p, n);  /* Routine che incrementa di n */
         sprintf( buf, "%d", sc_temp );
         break;
       ...
     }
     /* Visualizza su display il valore temporaneo utilizzando la stringa buf. */
   }
In qualche modo, le variabili statiche xx_temp dovranno essere inizializzate
al valore attuale del parametro, prima della modifica.
Che ne pensate?


In questo caso, con char a 8 bit e int a 16, tutti gli unsigned char sono
rappresentabili con int positivi. E siccome la rappresentazione dei positivi
è garantita essere la stessa dei corrispondenti unsigned, è la stessa cosa!
--
Giorgio Silvestri

 a unsigned char). Forse mi darebbe un errore o un warning.
Beh, se i dati nella union sono solo puntatori puoi "barare" mettendo
come primo campo un void*, in modo che l'assegnamento non generi mai un
warning.
Come alternativa il C99 supporta i "designed initializers",
permettendoti di indicare quale campo stai inizializzando:
const param myparam_schar = { PARAMTYPE_SCHAR, { .vptr_schar = &mychar } };
GCC e derivati (magari è il tuo caso) supportano da molto tempo la
stessa feature con una sintassi alternativa:
const param myparam_schar = { PARAMTYPE_SCHAR, { vptr_schar: &mychar } };
Luca
-- 
Home: 
http://kronoz.cjb.net
Porc i' mond che cio' sott i piedi!
V. Catozzo

 Che ne pensate?
Mostra del codice pronto e compilabile, con quello che vorresti fare.
--
Giorgio Silvestri

 debug del sistema di sviluppo stesso.
Sul mio c'è una printf() ma sinceramente non capisco a cosa serva e
come
venga utilizzata visto che non ho nessuna window di debug o cose
simili.

 Mostra del codice pronto e compilabile, con quello che vorresti fare.
Ho cercato di scrivere degli estratti di codice per evitare di postare
un sorgente lunghissimo.


Visto che abbiamo la ormai famosa integer-promotion questo non succede.
Il calcolo è fatto in 'int'.
--
Giorgio Silvestri