Newsgroup: it.comp.lang.c
Titolo: Buffer (void *) a cui accede byte a byte

Nelle mie applicazioni mi ritrovo spesso a dover passare ad una funzione il 
puntatore ad un buffer in memoria. Come ho visto fare nelle varie funzioni di 
libreria (vedi memcpy e simili), ho pensato di dichiarare il buffer come (void *).
In questo modo, il chiamante può passare un qualsiasi puntatore, senza fare cast.
All'interno della funzione, però, devo accedere al buffer scrivendolo byte a 
byte e quindi mi serve un (unsigned char *).
Normalmente faccio così:
   void dummy( void *bufp ) {
     unsigned char *buf = bufp;
     *bufp++ = 0xAA;
     *bufp++ = 0xAB;
     ..
Ma così perdo spazio sullo stack per bufp e per buf e mi sembra abbastanza inutile.


Probabilmente il compilatore se ne accorge e non ti fa perdere spazio 
per niente... In C++ userei invece un riferimento, in questo caso...
E.


Forse volevi scrivere:
   void dummy( void *bufp ) {
     unsigned char *buf = bufp;
     *buf++ = 0xAA;
     *buf++ = 0xAB;
     ..
In ogni caso se vuoi evitare di allocare una variabile nello stack 
puoi usare il cast:
void dummy(void *bufp)
{
   *((char *)bufp++) = 0xAA;
   *((char *)bufp++) = 0xAB;
   ....
}
Ciao
Giovanni
-- 
   A computer is like an air conditioner,
   it stops working when you open Windows.
   Registered Linux user #337974  < 
http://giovanni.homelinux.net/
 >

 per niente... In C++ userei invece un riferimento, in questo caso...
Sì, ci avevo pensato. Però ammettendo che il compilatore se ne accorga, non mi 
sembra uno stile di programmazione molto pulito... o sbaglio?
Come farebbe una memcpy(), per esempio?

 abbastanza inutile.
Perdi 4 o 8 byte per un puntatore in più nella peggiore delle ipotesi.
Sicuro che sia inaccettabile?
Aggiungo altre note... se conosci giù quello che ci devi scrivere,
perchè non ne fai un array di letterali ({0xAA, 0xAB, ...}) e lo copi
proprio con memcpy?
Se non usi questa strada, perchè non fattibile, io considererei
l'alternativa di una macro (da poi fare sparire) al posto di 
   *((char *)bufp++) = 0xAB;
#define SIGNIFICATIVE_NAME(pvoid, value)  \ 
        *((char *)(pvoid)++) = (value);
E a questo punto scavalli il tutto. Ovviamente a patto di trovare  un
nome significativo che renda *effettivamente* il codice più leggibile.
-- 
cc = callcc {|cc| cc.call cc}; cc.call cc

 ha scritto nel messaggio
news:fnhg50$jag$1...@milena.home.net.it...

Attenzione che non compila!
Il ++ viene associato direttamente a bufp non a (char *)bufp
Io preferisco la versione con la temporanea.
Più pulito.
--
Giorgio Silvestri
DSP/Embedded/Real Time OS   Software Engineer


Idem.


 Più pulito.
Su questo sono d'accordo.


 Stesso problema del post di Giovanni.
Vero. Non ci avevo pensato (ne lo avevo provato). Il fix è banale. Ma
non piace neppure a me. Io personalmente userei proprio il memcpy. Si sa
mica mai che se lo siano ottimizzato per bene.


 }
Giusto, però così diventa difficile da leggere e complicato da scrivere.
Poi quelle istruzioni sono solo un esempio. In alcuni casi devo scrivere dei 
byte con valore fisso, in altri casi devo copiare (mediante memcpy) delle 
variabili, in altri casi delle struct, ecc.

 nome significativo che renda *effettivamente* il codice più leggibile.
E' un'idea...

 struct, ecc.
Sul fatto che sia più complicato da scrivere e difficile da leggere 
concordo, ma se ti preoccupi di risparmiare anche il singolo byte di 
memoria è conveniente. Qualsiasi operazione tu debba fare il cast la 
può risolvere.  Per semplificare la scritture puoi usare delle macro:
#define bufp_c ((char *)bufp)		/* puntatore a char */
#define bufp_i ((int *)bufp)		/* puntatore a int */
#define bufp_s ((struct s *)bufp)	/* puntatore a struct s */
*(bufp_c)++ = 0x04
*(bufp_i)++ = 123456;
*(bufp_s)->membro_struttura = ....
#undef bufp_s
#undef bufp_i
#undef bufp_c
L'unico side effect che vedo è il fatto che in questi esempi vai a 
modificare il valore del parametro di ingresso.  In certe situazioni 
ed accessi multipli potrebbe servire il valore originale.
Tieni anche presente che usando l'ottimizzazione molti compilatori 
mettono la variabile in un registro ed anche tu lo puoi fare con la 
keyword register nella dichiarazione della puntatore d'appoggio. 
Attenzione però che a volte se la funzione è molto complessa, il 
compilatore ha bisogno dei registri ed usa lo stack per salvare 
temporaneamente il registro e non risparmia nulla.


 keyword register nella dichiarazione della puntatore d'appoggio. 
Tipicamente non farci conto: il compilatore fa un po' quello che vuole.
Non essendo tenuto a rispettare la direttiva, in molti i casi la ignora
e punto. :)


 
Difatti avevo scritto anche:


La soluzione piu' elegante, secondo il mio modesto parere, e' che
invece di dichiarare la tua funzione come se accettasse dei void *,
dovresti dichiararla con char *.
Motivo: la tua funzione e' effettivamente una funzione che opera con
l'aritmetica dei puntatori. Per cui la tua funzione ha bisogno di
accedere ai byte.
Il chiamante della tua funzione e' quello che si deve preoccupare di
fare il cast.
Dal punto di vista del compilatore e' piu' facile ottimizzare
l'allineamento dei byte sul chiamante che sul chiamato.
Perche' ricordati, con la dichiarazione void * stai chiedendo al
compilatore di accettare *sempre* un puntatore con allineamento
generico.
Siccome accedi ai singoli byte, se non ti fidi dell'ottimizzazione del
compilatore, effettivamente tu stai chiedendo al compilatore una cosa
non vera.
Tu hai bisogno, nella tua funzione, di avere un puntatore con massima
efficienza ad accesso di byte sequenziale.
Il compilatore ha piu' facilita' a capire la cosa nel codice
chiamante, in quanto il cast viene fatto una sola volta, e il
chiamante puo' usare ottimizzazioni piu' idonee per farlo.
Le implementazioni di memcpy che vedo in giro infatti contano sul
fatto che il processore possa fare delle copie _in blocco_, senza
quindi accedere ai singoli byte.
Le funzioni che operano su byte singoli di regola dichiarano i loro
puntatori come char *, e sta al chiamante decidere se la chiamata e'
opportuna o c'e' bisogno di un cast.
Dal punto di vista dell'interfaccia, che strcpy sia dichiarato con
char * mi fa capire che si sta parlando di byte signoli.
Void * mi fa capire che sia generico e quindi non ottimizzato per la
mia particolare struttura dati.
Questo e' il mio EURO 0.02

 Difatti avevo scritto anche:
Si, io criticavo il fatto che la funzione 'dovesse essere molto
complessa'. Cioč, č proprio un tipo di indicazione che IMHO viene spesse
volte ignorata bellamente di default.


 il cast, cioè: (char *)bufp
No il ++ viene associato (in questo caso) alla espressione postfissa
che lo precede che è  bufp e non (char *)bufp, basta fare l'albero di parse.
Cioè basta vedere la grammatica del linguaggio. (C99 par 6.5.1/6.5.2 ecc.)


 ha scritto nel messaggio
news:1ibdrzn.jxjmw2aqzoadN%ri...@despammed.com...

Anche io penso che alla fine il metodo del memcpy dovrebbe essere quello
migliore.
Le librerie sono normalmente ottimizzate per la particolare architettura.


 
Bah.  Io non vedo nessuna conferma della tua interpretazione della 
precedenza del ++ rispetto al cast.  Al contrario al punto 4 della 
sezione 6.5.4:
Preceding an expression by a parenthesized type name converts the 
value of the expression to the named type. This construction is called 
a cast.85) A cast that specifies no conversion has no effect on the 
type or value of an expression.86)
Seguendo questa regola il cast modifica il tipo quindi l'incremento 
dovrebbe essere applicato al nuovo tipo.  Data l'associazione 
left-to-right del cast e right-to-left del post-incremento si può 
anche interpretare che devi associare il nuovo tipo


Giovanni ha scritto:

Giovanni scusa ma sono inesperto..dici:
In ogni caso se vuoi evitare di allocare una variabile nello stack puoi 
usare il cast
se usi il cast dove vengono allocate?  grazie bruce

 se usi il cast dove vengono allocate?  grazie bruce
Usando il cast non si alloca una nuova variabile, ma si usa 
direttamente il puntatore passato, ma ne viene modificato il tipo.
bufp è dichiarato come puntatore ad un tipo void mentre col cast 
diventa un puntatore ad un tipo diverso (come specificato dal cast).
(int *)bufp diventa un puntatore ad un tipo int.
Attenzione però che l'espressione che ho scritto è sbagliata.  Come 
puoi vedere nelle discussione del thread in alcuni compilatori dà 
errore e nel gcc, pur compilando correttamente, dà risultati corretti 
solo usando il cast (char *).
L'espressione corretta richiede un livello ulteriore di parentesi.
*( ( (char *)bufp )++ )


 anche interpretare che devi associare il nuovo tipo
No. La grammatica parla chiaro.


 >>> 
...da' 'perlomeno' lo stesso warning con gcc :-)
ciao
-- 
Cybertoldo
Per rispondermi via email, sostituisci moc.liamg con 
gmail.com