Je sais que tout le monde déteste les gotos. Dans mon code, pour des raisons que j'ai prises en compte et avec lesquelles je suis à l'aise, elles constituent une solution efficace (par exemple, je ne cherche pas la réponse «ne le faites pas», je comprends vos réserves et vous comprenez pourquoi je les utilise. en tous cas).
Jusqu'à présent, elles ont été fantastiques, mais je souhaite développer les fonctionnalités de manière à ce que je sois capable de stocker des pointeurs sur les étiquettes, puis de les consulter plus tard.
Si ce code fonctionnait, il représenterait le type de fonctionnalité dont j'ai besoin. Mais cela ne fonctionne pas et 30 minutes de recherches sur Google n'ont rien révélé. Quelqu'un a-t-il une idée?
int main (void)
{
int i=1;
void* the_label_pointer;
the_label:
the_label_pointer = &the_label;
if( i-- )
goto *the_label_pointer;
return 0;
}
Les normes C et C++ ne prennent pas en charge cette fonctionnalité. Cependant, la collection de compilateurs GNU (GCC) comprend une extension non standard permettant cette opération, comme décrit dans cet article . Pour l'essentiel, ils ont ajouté un opérateur spécial "&&" qui indique l'adresse du libellé sous la forme "void *". Voir l'article pour plus de détails.
P.S. En d'autres termes, utilisez simplement "&&" au lieu de "&" dans votre exemple, et cela fonctionnera sous GCC.
P.P.S. Je sais que vous ne voulez pas que je le dise, mais je le dirai quand même, ... NE FAITES PAS CELA !!!
Vous pouvez faire quelque chose de similaire avec setjmp/longjmp.
int main (void)
{
jmp_buf buf;
int i=1;
// this acts sort of like a dynamic label
setjmp(buf);
if( i-- )
// and this effectively does a goto to the dynamic label
longjmp(buf, 1);
return 0;
}
Selon la norme C99, § 6.8.6, la syntaxe pour un goto
est la suivante:
aller àidentifiant;
Donc, même si vous pouviez prendre l'adresse d'une étiquette, vous ne pourriez pas l'utiliser avec goto.
Vous pouvez combiner un goto
avec un switch
, qui ressemble à un goto
calculé, pour un effet similaire:
int foo() {
static int i=0;
return i++;
}
int main(void) {
enum {
skip=-1,
run,
jump,
scamper
} label = skip;
#define STATE(lbl) case lbl: puts(#lbl); break
computeGoto:
switch (label) {
case skip: break;
STATE(run);
STATE(jump);
STATE(scamper);
default:
printf("Unknown state: %d\n", label);
exit(0);
}
#undef STATE
label = foo();
goto computeGoto;
}
Si vous utilisez ceci pour autre chose qu'un concours C obscurci, je vous traquerai et vous blesserai.
L'instruction switch ... case
est essentiellement un calculé goto
. Un bon exemple de son fonctionnement est le bidouillage bizarre connu sous le nom de Le périphérique de Duff :
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);
}
}
Vous ne pouvez pas créer un goto
à partir d'un emplacement arbitraire à l'aide de cette technique, mais vous pouvez envelopper toute votre fonction dans une instruction switch
basée sur une variable, puis définir cette variable en indiquant l'endroit où vous voulez aller et goto
cette instruction switch.
int main () {
int label = 0;
dispatch: switch (label) {
case 0:
label = some_computation();
goto dispatch;
case 1:
label = another_computation();
goto dispatch;
case 2:
return 0;
}
}
Bien sûr, si vous faites cela souvent, vous voudrez peut-être écrire des macros pour l'envelopper.
Cette technique, ainsi que certaines macros pratiques, peut même être utilisée pour implémenter coroutines dans C .
Dans la très très très ancienne version du langage C (pensez à l'époque où les dinosaures parcouraient la Terre), appelée version "C Reference Manual" (qui fait référence à un document écrit par Dennis Ritchie), les étiquettes avaient officiellement le type " array of int "(étrange, mais vrai), ce qui signifie que vous pouvez déclarer une variable int *
int *target;
et attribuer l'adresse de l'étiquette à cette variable
target = label; /* where `label` is some label */
Plus tard, vous pourrez utiliser cette variable comme opérande de l'instruction goto
goto target; /* jumps to label `label` */
Cependant, dans ANSI C, cette fonctionnalité a été supprimée. Dans le standard C moderne, vous ne pouvez pas prendre l’adresse d’une étiquette et vous ne pouvez pas faire de "paramétrisation" goto
. Ce comportement est censé être simulé avec des instructions switch
, des pointeurs vers des fonctions et d'autres méthodes, etc. En fait, même le "Manuel de référence C" lui-même disait que "les variables label sont une mauvaise idée en général; l'instruction switch les rend presque toujours inutiles "(voir " 14.4 Étiquettes " ).
Je sais que tout le monde a le sentiment que cela ne devrait pas être fait; c'est juste a à faire. Voici comment le faire:
#define jumpto(a) asm("jmp *%0"::"r"(a):)
int main (void)
{
int i=1;
void* the_label_pointer;
the_label:
the_label_pointer = &&the_label;
if( i-- )
jumpto(the_label_pointer);
return 0;
}
L'opérateur de déréférencement d'étiquettes && ne fonctionnera qu'avec gcc. Et bien entendu, la macro assemblage jumpto doit être implémentée spécifiquement pour chaque processeur (celui-ci fonctionne avec les systèmes x86 32 et 64 bits). N'oubliez pas non plus qu'il n'y a aucune garantie que l'état de la pile soit identique à deux endroits différents de la même fonction. Et au moins, avec une optimisation activée, il est possible que le compilateur suppose que certains registres contiennent une valeur au point situé après le libellé. Ce genre de choses peut facilement se faire foirer, alors faire une merde folle à laquelle le compilateur ne s'attend pas. Assurez-vous de lire le code compilé.
Je noterai que la fonctionnalité décrite ici (y compris && dans gcc) est idéale pour implémenter un interpréteur de langage Forth en C. Cela élimine tous les arguments du type «ne fais pas ça» - la correspondance entre cette fonctionnalité et le chemin L'interprète interne de Forth est trop beau pour être ignoré.
Utilisez des pointeurs de fonction et une boucle while. Ne créez pas de code que quelqu'un d'autre devra regretter d'avoir réparé pour vous.
Je suppose que vous essayez de changer l’adresse de l’étiquette d’une manière ou d’une autre en externe. Les pointeurs de fonction fonctionneront.
La seule chose que vous pouvez faire officiellement avec une étiquette en C est goto
. Comme vous l'avez remarqué, vous ne pouvez pas en prendre l'adresse ni la stocker dans une variable ou quoi que ce soit d'autre. Donc, au lieu de dire "ne fais pas ça", je vais dire "tu ne peux pas faire ça".
On dirait que vous devrez trouver une solution différente. Peut-être le langage de l’Assemblée, s’il est critique en termes de performances
Lisez ceci: setjmp.h - Wikipedia Comme indiqué précédemment, il est possible avec setjmp/longjmp de stocker un point de liaison dans une variable et de revenir en arrière ultérieurement.
#include <stdio.h>
int main(void) {
void *fns[3] = {&&one, &&two, &&three};
char p;
p = -1;
goto start; end: return 0;
start: p++;
goto *fns[p];
one: printf("hello ");
goto start;
two: printf("World. \n");
goto start;
three: goto end;
}
Vous pouvez faire quelque chose comme l'ordinateur goto de Fortran avec des pointeurs vers des fonctions.
// variables globales ici
void c1 () {// morceau de code
}
void c2 () {// morceau de code
}
void c3 () { // morceau de code
}
void (* goTo [3]) (void) = {c1, c2, c3};
// puis
int x = 0;goTo [x ++] ();
goTo [x ++] ();
goTo [x ++] ();
Vous pouvez assigner une étiquette à une variable avec && . Voici votre code modifié.
int main (void)
{
int i=1;
void* the_label_pointer = &&the_label;
the_label:
if( i-- )
goto *the_label_pointer;
return 0;
}
Selon ce fil , les points d’étiquetage ne sont pas standard, leur fonctionnement dépend du compilateur que vous utilisez.