[OT - Programm. C] Mi spiegate il dispositivo di Duff?

Sto leggendo da un paio di orette come funziona il meccanismo dei protothread ma non riesco a capire il funzionamento del dispositivo di Duff.
http://groups.google.com/group/comp.lang.c/msg/bb78298175c42411?dmode=source
http://www.chiark.greenend.org.uk/~sgtatham/coroutines.html

Nei protothread è usato per creare questo switch:

char example(struct pt *pt)
{
  switch(pt->lc) { case 0:
 
  while(1) {
    pt->lc = 12; case 12:
    if(!(counter == 1000)) return 0;
    printf("Threshold reached\n");
    counter = 0;
  }
 
  } pt->lc = 0; return 2;
}

L'autore dice poi:

Did you notice anything slightly strange with the code above? The switch(pt->lc) statement jumped right into the while(1) loop. The case 12: statement was inside the while(1) loop! Does this really work? Yes, it does in fact work! This feature of the C programming language was probably first found by Tom Duff in his wonderful programming trick dubbed Duff's Device. The same trick has also been used by Simon Tatham to implement coroutines in C (a terrific piece of code).

Arrivo al fatto che il case 0 viene eseguito subito e cambia state a 12. Ma perché dopo salta dentro al secondo switch?
Anche il dispositivo originale di Duff non mi è chiaro:

switch (count % 8) {
        case 0:        do {  *to = *from++;
        case 7:              *to = *from++;
        case 6:              *to = *from++;
        case 5:              *to = *from++;
        case 4:              *to = *from++;
        case 3:              *to = *from++;
        case 2:              *to = *from++;
        case 1:              *to = *from++;
                       } while ((count -= 8) > 0);
}

Leo io il codice lo capisco, quello che non mi appare lampante è il motivo di ciò.

exmples viene eseguita in loop, ma ritorna sempre 0, a meno che counter sia uguale a 1000 allora stampa "Threshold reached\n", azzera il contatore, azzera lc e ritorna 2.

A che serve?

Ciao.

Sarà il freddo, però mi viene in mente solo questo :grin:

astrobeed:
Sarà il freddo, però mi viene in mente solo questo :grin:

questi sono i momenti in cui si sente la necessità del tasto "mi piace" :smiley:

--> Duff's device - Wikipedia
Non ho capito una mazza. :astonished:

Allora, vediamo di fare chiarezza.

Il motivo dello studio di quel codice è per capire i protothread, cioè la possibilità di poter eseguire dei compiti in programmazione simil-multithread senza avere un consumo eccessivo di risorse né di flash né di ram.
Il dispositivo di Duff sfrutta un "trick" del C che permette di eseguire codice all'interno di switch..case saltando attraverso i vari case: e costruendo dei cicli annidati nei case:

All'occhio tali cicli sembrano errati ma in realtà il compilatore C li digerisce e li ottimizza quasi quanto i cicli in assembly.

Il dispositivo di Duff originale era questo:

send(to, from, count)
register short *to, *from;
register count;
{
        register n = (count + 7) / 8;
        switch(count % 8) {
        case 0:      do {     *to = *from++;
        case 7:              *to = *from++;
        case 6:              *to = *from++;
        case 5:              *to = *from++;
        case 4:              *to = *from++;
        case 3:              *to = *from++;
        case 2:              *to = *from++;
        case 1:              *to = *from++;
                } while(--n > 0);
        }
}

Il codice sfrutta il fatto che i case: senza break non "terminano" con l'incontro del case: successivo. Vedete infatti che nel case 0 viene aperto un altro ciclo do..while, costruito all'interno dei case: stessi. Codice astruso, all'apparenza errato, ma valido per il compilatore.

Il codice dei protothread fa una cosa simile. Ecco un esempio di protothread, costruito usando le macro definite dalla libreria:

static
PT_THREAD(example(struct pt *pt))
{
  PT_BEGIN(pt);
  
  while(1) {
    PT_WAIT_UNTIL(pt,
      counter == 1000);
    printf("Threshold reached\n");
    counter = 0;
  }
  
  PT_END(pt);
}

Il compilatore lo traduce in questo codice C puro:

static char example(struct pt *pt)
{
  switch(pt->lc) { case 0:
 
  while(1) {
    pt->lc = 12; case 12:
    if(!(counter == 1000)) return 0;
    printf("Threshold reached\n");
    counter = 0;
  }
 
  } pt->lc = 0; return 2;
}

Anche qui vedete come il case 0 costruisca un ciclo while interno. Eseguito la prima volta (basta assegnare a pt->lc il valore di 0), in esso viene assegnato a pt->lc il valore 12, così che successivamente l'esecuzione salti al case 12 che, curiosamente, è interno ad un while! Ma il compilatore non genera errori ma accetta e compila il codice, che viene regolarmente eseguito.

Questi dispositivi di Duff sono utilizzati per ridurre drasticamente il tempo di esecuzione dei cicli stessi. Sono nati osservando come il codice in assembly generato da uno switch..case fatto così fosse molto più snello rispetto ad un ciclo for e successivi break.
Insomma, è una cosa che, per me che non so molto di C "vecchia scuola" è difficile da capire.

Ridomando: come funziona? :sweat_smile: