Goto

Tags: Avancé

Les fonctions et structures de controle permettent de structurer et de décrire de manière lisible le fil d’un programme, contrairement à un goto, mais il existe des cas d’usages où l’usage d’un goto améliore la lisibilité d’un programme, voire est indispensable.

Contrairement aux appels de fonction qui utilisent un pile d’appels qui peut déborder, un goto ne peut jamais connaitre de stack overflow. Si un algorithme est plus élégant avec des appels récursifs mais présente le risque de débordement de mémoire, stocker les états dans une liste chainée et simuler les appels de fonctions récursifs peut être un paliatif technique.

Instructions

Un goto ne peut être associé qu’à une instruction et non à une expression. Les codes qui suivent ne sont pas valides


int main() {
  printf(toto:"texte"); // "texte" est un paramêtre
  int a = 42 ? 12 : tata:21;  // "21" est une expression et non une instruction
}

Jusqu’au C23 si on voulait sauter à la fin d’un bloc, il fallait obligatoirement lui assigner une instruction vide ; : typiquement

int main() {
  {
    goto ici;

    int toto;

    ici:; // noter l'instruction vide ";"
  }
}

Depuis le C23 on peut assigner une destination de saut à une accolade fermante de fin de bloc.

Saut vers des blocs

Si on saute depuis l’exterieur d’un bloc vers l’interieur de celui-ci, l’espace pour les variables du bloc est alloué, mais si la destination de saut est après leur initialisation, elles seront non initialisées, c’est a dire

int main() {
  
  int toto = 12;
  int tata = 14;
  goto ici;

  {
    int toto = 24;
    int titi = 30;

    ici:
    int tata = 28;

    printf("toto == %d", toto); // valeur indéfinie, non 24
    printf("tata == %d", tata); // 28
    printf("toto == %d", titi); // valeur indéfinie, non 30
  }

  printf("toto == %d", toto); // 12
  printf("tata == %d", tata); // 14

}

De la même façon dans les instructions switch les variables de bloc ont des valeurs indéfinies selon le lieu d’aterrissage

int main() {
  
  int toto = 12;
  int tata = 14;

  switch(toto){
    int toto = 24;
    int tata = 28;

    case 12:
    printf("toto == %d", toto); // valeur indéfinie, non 24
    break;

    case 14:
    printf("tata == %d", tata); // valeur indéfinie, non 28
    break;

    default:
    toto = 20; // seulement à partir de ce point "toto" a une valeur définie
    printf("toto == %d", toto); // 20
  }

  printf("toto == %d", toto); // 12
  printf("tata == %d", tata); // 14

}

int toto = 24; en C est un sucre syntaxique qui consiste à écrire en une ligne

int toto;
toto = 24;

Si la machine virtuelle agrandit automatiquement pour nous la pile, assigner des valeurs aux variables de la pile est laissé au programmeur.

C++ : Le c++ n’autorise pas de pouvoir sauter par dessus l’initialisation de variables au sein d’un bloc comme nous venons de le voir, donc si vous écriver une instruction switch et que vous déclarez des variables à la suite d’un case ou n’importe où dans un bloc d’un switch, vous devrez obligatoirement isoler ces variables dans un bloc.

Sauts inter fonctions

On peut sauter vers et depuis n’importe quel bloc au sein d’une fonction, mais on ne peut pas sauter vers d’autres fonctions avec goto. Une étiquette de saut a une portée limitée à sa fonction.

int fun1() 
{
  toto: 
  
  {

    toto:; // interdit
  }

  return 0;
}

int fun2()
{

  toto:  // autorisé , deux fonction peuvent déclarer la même étiquette

  return 0;
}

Pour sauter entre deux fonctions, il faut utiliser setjmp et lngjmp .