web-dev-qa-db-fra.com

Est-il possible de stocker l'adresse d'une étiquette dans une variable et d'utiliser goto pour y accéder?

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;
}
48
Joshua Cheek

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 !!!

58

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;
}
16
R Samuel Klatchko

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.

12
outis

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 .

9
Brian Campbell

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 " ).

9
AnT

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é.

7
Fabel

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é.

5
Kip Ingram

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.

4
user208608

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

3
Greg Hewgill

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.

1
icefex
#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;
}
1
RUE_MOHR

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 ++] ();

0
QuentinUK

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;
}
0
Mayank

Selon ce fil , les points d’étiquetage ne sont pas standard, leur fonctionnement dépend du compilateur que vous utilisez.

0
Kaleb Brasee