Chose étrange...

7 réponses
Avatar
JKB
Bonjour à tous,

Je suis en train d'essayer de comprendre un bout de code
issu de Contiki pour débugguer un firmware assez spécifique et je
tombe sur des choses bizarres avec des switches comme :

{
switch(a);
...
{
case a:
...
case b:
...
}

for(...;...;...)
{
do
{
...
case c:; // <- ??????
} while(0);
}
...
}

Naturellement, ces bouts de code sont masqués dans des macros (que
j'ai remplacées par leurs définitions car les macros ne font pas ce
qu'elles sont censées faire dans la doc...).

Ce code compile sur le cross-compilateur fourni par le fabricant
pour sa plate-forme. Pourtant, le 'case c:' ne me semble pas licite,
la construction 'switch' étant à cheval sur au moins une boucle
issue de la macro elle-même ! Un avis autorisé ?

Cordialement,

JKB

--
Si votre demande me parvient sur carte perforée, je titiouaillerai très
volontiers une réponse...
=> http://grincheux.de-charybde-en-scylla.fr

7 réponses

Avatar
espie
In article ,
JKB wrote:
Bonjour à tous,

Je suis en train d'essayer de comprendre un bout de code
issu de Contiki pour débugguer un firmware assez spécifique et je
tombe sur des choses bizarres avec des switches comme :

{
switch(a);


ben deja, le ; la me semble suspect.
...
{
case a:
...
case b:
...
}

for(...;...;...)
{
do
{
...
case c:; // <- ??????
} while(0);
}
...
}

Naturellement, ces bouts de code sont masqués dans des macros (que
j'ai remplacées par leurs définitions car les macros ne font pas ce
qu'elles sont censées faire dans la doc...).

Ce code compile sur le cross-compilateur fourni par le fabricant
pour sa plate-forme. Pourtant, le 'case c:' ne me semble pas licite,
la construction 'switch' étant à cheval sur au moins une boucle
issue de la macro elle-même ! Un avis autorisé ?



Tu voudrais pas nous montrer un fragment COMPLET reellement issu du
preprocesseur, meme si c'est tres moche ?

Je pense que ce que tu as la, ca doit etre une variante de Duff's device
(confere wikipedia ou assimilie) qui est a la fois un des hacks les plus
crades et les plus "beaux" du langage, mais vu le ; en trop, je soupconne
que tu t'es aussi trompe sur les imbrications d'accolades dans le code
genere, et que c'est plutot un truc du genre:

switch(e) {
case a:
...
case b:
...

for (;;) {
do {
case c: ;
} while (0);
}
}

ce qui, meme si assez bizarre, est licite (meme si ca ressemble beaucoup
a un goto en plein milieu du code, et donc qu'il se peut que certaines
initialisations manquent).
Avatar
Samuel DEVULDER
Le 31/07/2012 16:19, JKB a écrit :

switch(a);
...
{
case a:
...
case b:
...
}

for(...;...;...)
{
do
{
...
case c:; //<- ??????
} while(0);
}
...
}



J'ai vu passer un jour des trucs dans le même genre avec un switch
et des case au milieu de blocks pour pouvoir faire un code qui
fonctionne dans l'esprit des continuations (je ne suis plus très sur
de ce mot). En gros toutes les variables C sont dans une structure
appelée contexte, et cette dernière contient un champ "état" qui sert au
switch pour reprendre l'algo à son dernier point d’exécution.
Cela permet de tronçonner l’exécution d'un algorithme autrement
monolithique.

Voyez ci bas la présence de case en plein milieu de if(), avec des
accolades apriori mal appariées.

----8<---------------------------------------------------------------
int
exo_read_decrunched_byte(struct exo_decrunch_ctx *ctx)
{
int c;
int length_index;
struct exo_table_entry *table_entry;

switch(ctx->state)
{
case STATE_NEXT_BYTE:
if(read_bits(ctx, 1) == 1)
{
/* literal byte */
c = ctx->read_byte(ctx->read_data);
break;
}
/* sequence */
length_index = get_gamma_code(ctx);
if(length_index == 17)
{
/* literal data block */
ctx->length = read_bits(ctx, 16);
ctx->state = STATE_NEXT_LITERAL_BYTE;
case STATE_NEXT_LITERAL_BYTE:
if(--ctx->length == 0)
{
ctx->state = STATE_NEXT_BYTE;
}
c = ctx->read_byte(ctx->read_data);
break;
}
else if(length_index == 16)
{
/* end of data marker, we're done. */
ctx->state = STATE_EOF;
case STATE_EOF:
return -1;
}
/* sequence */
table_entry = ctx->lengths + length_index;
ctx->length = table_entry->base + read_bits(ctx,
table_entry->bits);
switch(ctx->length)
{
case 1:
table_entry = ctx->offsets1 + read_bits(ctx, 2);
break;
case 2:
table_entry = ctx->offsets2 + read_bits(ctx, 4);
break;
default:
table_entry = ctx->offsets3 + read_bits(ctx, 4);
}
ctx->offset = table_entry->base + read_bits(ctx,
table_entry->bits);
ctx->state = STATE_NEXT_SEQUENCE_BYTE;
case STATE_NEXT_SEQUENCE_BYTE:
if(--ctx->length == 0)
{
ctx->state = STATE_NEXT_BYTE;
}
c = read_byte_from_window(ctx, ctx->offset);
break;
}

ctx->window[ctx->window_pos++] = (unsigned char)c;
if(ctx->window_pos == ctx->window_length)
{
ctx->window_pos = 0;
}
return c;
}
----8<---------------------------------------------------------------

Ce code est vraiment tordu, il y a des case en plein milieu de if(),
des fall-though à la fin des cases,... aie aie aie.. et pourtant il
se compile très bien. De ce que j'en ai pensé c'est que le case n'est
finalement jamais plus qu'une sorte d'étiquette... mais une étiquette
sans goto car le goto-c'est-mal(tm). Le saut est simplement calculé lors
du switch(). C'est un vrai truc de tricheur en somme cette histoire ;-)

sam.
Avatar
JKB
Le Tue, 31 Jul 2012 16:58:03 +0000 (UTC),
Marc Espie écrivait :
In article ,
JKB wrote:
Bonjour à tous,

Je suis en train d'essayer de comprendre un bout de code
issu de Contiki pour débugguer un firmware assez spécifique et je
tombe sur des choses bizarres avec des switches comme :

{
switch(a);


ben deja, le ; la me semble suspect.



Oui, typo de ma part...

...
{
case a:
...
case b:
...
}

for(...;...;...)
{
do
{
...
case c:; // <- ??????
} while(0);
}
...
}

Naturellement, ces bouts de code sont masqués dans des macros (que
j'ai remplacées par leurs définitions car les macros ne font pas ce
qu'elles sont censées faire dans la doc...).

Ce code compile sur le cross-compilateur fourni par le fabricant
pour sa plate-forme. Pourtant, le 'case c:' ne me semble pas licite,
la construction 'switch' étant à cheval sur au moins une boucle
issue de la macro elle-même ! Un avis autorisé ?



Tu voudrais pas nous montrer un fragment COMPLET reellement issu du
preprocesseur, meme si c'est tres moche ?



C'est très moche et sur une machine de dev pas reliée au réseau.

Je pense que ce que tu as la, ca doit etre une variante de Duff's device
(confere wikipedia ou assimilie) qui est a la fois un des hacks les plus
crades et les plus "beaux" du langage, mais vu le ; en trop, je soupconne
que tu t'es aussi trompe sur les imbrications d'accolades dans le code
genere, et que c'est plutot un truc du genre:

switch(e) {
case a:
...
case b:
...
for (;;) {
do {
case c: ;
} while (0);
}
}

ce qui, meme si assez bizarre, est licite (meme si ca ressemble beaucoup
a un goto en plein milieu du code, et donc qu'il se peut que certaines
initialisations manquent).



Je vais essayer de copier une sortie du préprocesseur. Mais il est
vrai qu'à bien y réfléchir l'accolade après le case b: semble en
trop. Ce qui me chiffonnait, c'est qu'on puisse sauter directement
au case c: en sautant l'initialisation de for() et de do {. Ça doit
faire un vrai bazar sur la pile...

Cordialement,

JBK

--
Si votre demande me parvient sur carte perforée, je titiouaillerai très
volontiers une réponse...
=> http://grincheux.de-charybde-en-scylla.fr
Avatar
JKB
Le Tue, 31 Jul 2012 21:02:50 +0200,
Samuel DEVULDER écrivait :
Le 31/07/2012 16:19, JKB a écrit :

switch(a);
...
{
case a:
...
case b:
...
}

for(...;...;...)
{
do
{
...
case c:; //<- ??????
} while(0);
}
...
}



J'ai vu passer un jour des trucs dans le même genre avec un switch
et des case au milieu de blocks pour pouvoir faire un code qui
fonctionne dans l'esprit des continuations (je ne suis plus très sur
de ce mot). En gros toutes les variables C sont dans une structure
appelée contexte, et cette dernière contient un champ "état" qui sert au
switch pour reprendre l'algo à son dernier point d’exécution.
Cela permet de tronçonner l’exécution d'un algorithme autrement
monolithique.

Voyez ci bas la présence de case en plein milieu de if(), avec des
accolades apriori mal appariées.

----8<---------------------------------------------------------------
int
exo_read_decrunched_byte(struct exo_decrunch_ctx *ctx)
{
int c;
int length_index;
struct exo_table_entry *table_entry;

switch(ctx->state)
{
case STATE_NEXT_BYTE:
if(read_bits(ctx, 1) == 1)
{
/* literal byte */
c = ctx->read_byte(ctx->read_data);
break;
}
/* sequence */
length_index = get_gamma_code(ctx);
if(length_index == 17)
{
/* literal data block */
ctx->length = read_bits(ctx, 16);
ctx->state = STATE_NEXT_LITERAL_BYTE;
case STATE_NEXT_LITERAL_BYTE:
if(--ctx->length == 0)
{
ctx->state = STATE_NEXT_BYTE;
}
c = ctx->read_byte(ctx->read_data);
break;
}
else if(length_index == 16)
{
/* end of data marker, we're done. */
ctx->state = STATE_EOF;
case STATE_EOF:
return -1;
}
/* sequence */
table_entry = ctx->lengths + length_index;
ctx->length = table_entry->base + read_bits(ctx,
table_entry->bits);
switch(ctx->length)
{
case 1:
table_entry = ctx->offsets1 + read_bits(ctx, 2);
break;
case 2:
table_entry = ctx->offsets2 + read_bits(ctx, 4);
break;
default:
table_entry = ctx->offsets3 + read_bits(ctx, 4);
}
ctx->offset = table_entry->base + read_bits(ctx,
table_entry->bits);
ctx->state = STATE_NEXT_SEQUENCE_BYTE;
case STATE_NEXT_SEQUENCE_BYTE:
if(--ctx->length == 0)
{
ctx->state = STATE_NEXT_BYTE;
}
c = read_byte_from_window(ctx, ctx->offset);
break;
}

ctx->window[ctx->window_pos++] = (unsigned char)c;
if(ctx->window_pos == ctx->window_length)
{
ctx->window_pos = 0;
}
return c;
}
----8<---------------------------------------------------------------

Ce code est vraiment tordu, il y a des case en plein milieu de if(),
des fall-though à la fin des cases,... aie aie aie.. et pourtant il
se compile très bien. De ce que j'en ai pensé c'est que le case n'est
finalement jamais plus qu'une sorte d'étiquette... mais une étiquette
sans goto car le goto-c'est-mal(tm). Le saut est simplement calculé lors
du switch(). C'est un vrai truc de tricheur en somme cette histoire ;-)



Nous sommes parfaitement d'accord. Je me demande si c'est très beau
ou très crade... ;-)

Cordialement,

JKB

--
Si votre demande me parvient sur carte perforée, je titiouaillerai très
volontiers une réponse...
=> http://grincheux.de-charybde-en-scylla.fr
Avatar
espie
In article ,
JKB wrote:
Je vais essayer de copier une sortie du préprocesseur. Mais il est
vrai qu'à bien y réfléchir l'accolade après le case b: semble en
trop. Ce qui me chiffonnait, c'est qu'on puisse sauter directement
au case c: en sautant l'initialisation de for() et de do {. Ça doit
faire un vrai bazar sur la pile...



Non, en fait, ca doit pas faire 'un vrai bazar sur la pile'. Plus precisement,
c'est cense marcher. En general, sur la plupart des systemes, tu etablis
une stack-frame en debut de fonction et tu n'y touches plus.
Si c'est pas le cas, c'est le boulot du compilo de mettre les instructions
qui vont bien pour ajuster la stack-frame au moment du saut au case qui
nous interesse !

Tout ce qui va manquer, c'est des valeurs initiales a tes variables, s'il y
en a. Ou plus exactement, elles ne changeront pas de valeur grace a l'entree
dans le for(). On peut supposer que ton code:
- soit a initialise les variables en question avant,
- soit ne s'en sert pas apres le saut.

(sinon, un gcc des familles sera tres content de te balancer de gros
warnings de variables utilisees sans etre initialisees).

Bref, c'est pas plus "moche" que du code C normal, ou il est licite
d'avoir des zones de pile indefinies (variables pas encore initialisees)
tant que tu n'utilises pas les variables en question.

Comme l'a fort justement fait remarquer Sam, ca peut etre une facon de
programmer a base de continuations, ou de faire du code qui se base sur
des automates. C'est globalement tres imbuvable pour un etre humain
qui n'a pas l'habitude de ce genre de programmation, et ca reclame
effectivement une patience de jesuite pour etre sur que tout est bien
defini correctement. Mais bon, c'est bien d'avoir ce type de technique
disponible en C, ca serait encore plus chiant a faire directement
en assembleur.

Note que pas mal de generateurs de code utilisent des astuces assez
similaires.
Avatar
espie
In article <50182bdd$0$6477$,
Samuel DEVULDER wrote:
int
exo_read_decrunched_byte(struct exo_decrunch_ctx *ctx)
{
int c;
int length_index;
struct exo_table_entry *table_entry;

switch(ctx->state)
{
case STATE_NEXT_BYTE:
if(read_bits(ctx, 1) == 1)
{
/* literal byte */
c = ctx->read_byte(ctx->read_data);
break;
}
/* sequence */
length_index = get_gamma_code(ctx);
if(length_index == 17)
{
/* literal data block */
ctx->length = read_bits(ctx, 16);
ctx->state = STATE_NEXT_LITERAL_BYTE;
case STATE_NEXT_LITERAL_BYTE:
if(--ctx->length == 0)
{
ctx->state = STATE_NEXT_BYTE;
}
c = ctx->read_byte(ctx->read_data);
break;
}
else if(length_index == 16)
{
/* end of data marker, we're done. */
ctx->state = STATE_EOF;
case STATE_EOF:
return -1;
}
/* sequence */
table_entry = ctx->lengths + length_index;
ctx->length = table_entry->base + read_bits(ctx,
table_entry->bits);
switch(ctx->length)
{
case 1:
table_entry = ctx->offsets1 + read_bits(ctx, 2);
break;
case 2:
table_entry = ctx->offsets2 + read_bits(ctx, 4);
break;
default:
table_entry = ctx->offsets3 + read_bits(ctx, 4);
}
ctx->offset = table_entry->base + read_bits(ctx,
table_entry->bits);
ctx->state = STATE_NEXT_SEQUENCE_BYTE;
case STATE_NEXT_SEQUENCE_BYTE:
if(--ctx->length == 0)
{
ctx->state = STATE_NEXT_BYTE;
}
c = read_byte_from_window(ctx, ctx->offset);
break;
}

ctx->window[ctx->window_pos++] = (unsigned char)c;
if(ctx->window_pos == ctx->window_length)
{
ctx->window_pos = 0;
}
return c;
}



Mais c'est tres joli comme code. Ca permet d'avoir l'illusion qu'on lit
un flux continu et de derouler l'algo dans l'ordre, alors qu'en fait,
on lit le flux par morceaux.

Je dirais meme plus: dans ce contexte, c'est sans doute la facon la
plus propre et la moins sujette a erreur de faire.
Avatar
Antoine Leca
JKB écrivit :
Ce qui me chiffonnait, c'est qu'on puisse sauter directement
au case c: en sautant l'initialisation de for() et de do {. Ça doit
faire un vrai bazar sur la pile...



Pas ton problème ! :-)

Le « Duff's device » est un morceau de code C standard et archi connu
des implémenteurs, donc tous les compilateurs C doivent être capables de
le digérer (sinon « ce ne sont plus des compilateurs C ».)

J'irais même plus loin : si un fabricant de compilateur essaye de mettre
en place une option d'optimisation qui soit incompatible avec cette
construction, je pense qu'il sera fustigé pour cela, je ne pense pas
qu'une telle option puisse être activée par défaut, et à tout le moins
la documentation de cette option précisera clairement l'incompatibilité
avec la norme.


Antoine