Pourquoi le compilateur ne signale-t-il pas un point-virgule manquant?

115

J'ai ce programme simple:

#include <stdio.h>

struct S
{
    int i;
};

void swap(struct S *a, struct S *b)
{
    struct S temp;
    temp = *a    /* Oops, missing a semicolon here... */
    *a = *b;
    *b = temp;
}

int main(void)
{
    struct S a = { 1 };
    struct S b = { 2 };

    swap(&a, &b);
}

Comme vu par exemple sur ideone.com, cela donne une erreur:

prog.c: In function 'swap':
prog.c:12:5: error: invalid operands to binary * (have 'struct S' and 'struct S *')
     *a = *b;
     ^

Pourquoi le compilateur ne détecte-t-il pas le point-virgule manquant?


Remarque: Cette question et sa réponse sont motivées par cette question . Bien qu'il y ait d' autres questions similaires à celle-ci, je n'ai rien trouvé mentionnant la capacité de forme libre du langage C qui est à l'origine de cela et des erreurs associées.

Un mec programmeur
la source
16
Qu'est-ce qui a motivé ce post?
R Sahu
10
Découvrabilité @TavianBarnes. L'autre question n'est pas décelable lors de la recherche de ce type de problème. Il pourrait être modifié de cette façon, mais cela nécessiterait de changer un peu trop, ce qui en fait une question complètement différente de l'OMI.
Un mec programmeur
4
@TavianBarnes: La question d'origine demandait l'erreur. Cette question se demande pourquoi le compilateur semble (au moins à l'OP) déclarer mal l'emplacement de l'erreur.
TonyK
80
Point à méditer: si un compilateur pouvait détecter systématiquement des points-virgules manquants, le langage n'aurait pas besoin de points-virgules pour commencer.
Euro Micelli
5
Le travail des compilateurs est de signaler l'erreur. C'est votre travail de déterminer ce qu'il faut changer pour corriger l'erreur.
David Schwartz

Réponses:

213

C est un langage de forme libre . Cela signifie que vous pouvez le formater de plusieurs façons et que ce sera toujours un programme légal.

Par exemple, une déclaration comme

a = b * c;

pourrait être écrit comme

a=b*c;

ou comme

a
=
b
*
c
;

Donc, quand le compilateur voit les lignes

temp = *a
*a = *b;

ça pense que ça veut dire

temp = *a * a = *b;

Ce n'est bien sûr pas une expression valide et le compilateur s'en plaindra au lieu du point-virgule manquant. La raison pour laquelle ce n'est pas valide est parce que ac'est un pointeur vers une structure, donc *a * aessaie de multiplier une instance de structure ( *a) avec un pointeur vers une structure ( a).

Bien que le compilateur ne puisse pas détecter le point-virgule manquant, il signale également l'erreur totalement indépendante sur la mauvaise ligne. Ceci est important à noter car peu importe combien vous regardez la ligne où l'erreur est signalée, il n'y a pas d'erreur. Parfois, des problèmes comme celui-ci vous obligeront à regarder les lignes précédentes pour voir si elles sont correctes et sans erreurs.

Parfois, vous devez même chercher dans un autre fichier pour trouver l'erreur. Par exemple, si un fichier d'en-tête définit une structure la dernière fois qu'il le fait dans le fichier d'en-tête et que le point-virgule terminant la structure est manquant, l'erreur ne sera pas dans le fichier d'en-tête mais dans le fichier qui comprend le fichier d'en-tête.

Et parfois, cela devient encore pire: si vous incluez deux (ou plus) fichiers d'en-tête et que le premier contient une déclaration incomplète, l'erreur de syntaxe sera très probablement indiquée dans le deuxième fichier d'en-tête.


Le concept des erreurs de suivi est lié à cela . Certaines erreurs, généralement dues à des points-virgules manquants, sont signalées comme des erreurs multiples . C'est pourquoi il est important de commencer par le haut lors de la correction des erreurs, car la correction de la première erreur peut faire disparaître plusieurs erreurs.

Cela peut bien sûr conduire à corriger une erreur à la fois et à des recompilations fréquentes, ce qui peut être fastidieux avec de grands projets. Cependant, reconnaître de telles erreurs de suivi est quelque chose qui vient avec l'expérience, et après les avoir vues plusieurs fois, il est plus facile de déterrer les vraies erreurs et de corriger plus d'une erreur par recompilation.

Joachim Pileborg
la source
16
En C ++, temp = *a * a = *b pourrait être une expression valide si elles operator*étaient surchargées. (La question est étiquetée «C», cependant.)
dan04
13
@ dan04: Si quelqu'un a fait ça ... NOPE!
Kevin
2
+1 pour les conseils sur (a) en commençant par la première erreur signalée; et (b) en regardant en arrière à partir de l'endroit où l'erreur est signalée. Vous savez que vous êtes un vrai programmeur lorsque vous regardez automatiquement sur la ligne avant où une erreur est signalée :-)
TripeHound
@TripeHound SURTOUT quand il y a un très grand nombre d'erreurs, ou des lignes qui précédemment compilées lancent des erreurs ...
Tin Wizard
1
Comme c'est généralement le cas avec meta, quelqu'un a déjà demandé - meta.stackoverflow.com/questions/266663/…
StoryTeller - Unslander Monica
27

Pourquoi le compilateur ne détecte-t-il pas le point-virgule manquant?

Il y a trois choses à retenir.

  1. Les fins de ligne en C ne sont que des espaces blancs ordinaires.
  2. *en C peut être à la fois un opérateur unaire et un opérateur binaire. En tant qu'opérateur unaire, cela signifie «déréférencer», en tant qu'opérateur binaire, cela signifie «multiplier».
  3. La différence entre les opérateurs unaires et binaires est déterminée à partir du contexte dans lequel ils sont vus.

Le résultat de ces deux faits est lorsque nous analysons.

 temp = *a    /* Oops, missing a semicolon here... */
 *a = *b;

Le premier et le dernier *sont interprétés comme unaires mais le second *est interprété comme binaire. Du point de vue de la syntaxe, cela semble correct.

Ce n'est qu'après l'analyse que le compilateur essaie d'interpréter les opérateurs dans le contexte de leurs types d'opérandes qu'une erreur se produit.

plugwash
la source
4

Quelques bonnes réponses ci-dessus, mais je vais élaborer.

temp = *a *a = *b;

C'est en fait un cas x = y = z;où les deux xet se yvoient attribuer la valeur de z.

Ce que vous dites est the contents of address (a times a) become equal to the contents of b, as does temp.

En bref, *a *a = <any integer value>est une déclaration valide. Comme indiqué précédemment, le premier *déréférence un pointeur, tandis que le second multiplie deux valeurs.

Mawg dit de réintégrer Monica
la source
3
Le déréférencement est prioritaire, donc c'est (contenu de l'adresse a) fois (pointeur vers a). Vous pouvez le dire, car l'erreur de compilation dit "opérandes invalides en binaire * (ont 'struct S' et 'struct S *')" qui sont ces deux types.
dascandy
Je code avant C99, donc pas d'idiots :-) Mais vous faites un bon point (+1), bien que l'ordre d'affectation n'était pas vraiment le point de ma réponse
Mawg dit de réintégrer Monica
1
Mais dans ce cas, ce yn'est même pas une variable, c'est l'expression *a *a, et vous ne pouvez pas attribuer le résultat d'une multiplication.
Barmar
@Barmar en effet mais le compilateur ne va pas jusque-là, il a déjà décidé que les opérandes du "binaire *" ne sont pas valides avant de regarder l'opérateur d'affectation.
plugwash
3

La plupart des compilateurs analysent les fichiers sources dans l'ordre et signalent la ligne où ils découvrent que quelque chose n'allait pas. Les 12 premières lignes de votre programme C pourraient être le début d'un programme C valide (sans erreur). Les 13 premières lignes de votre programme ne le peuvent pas. Certains compilateurs noteront l'emplacement des choses qu'ils rencontrent qui ne sont pas des erreurs en eux-mêmes et, dans la plupart des cas, ne déclencheront pas d'erreurs plus tard dans le code, mais pourraient ne pas être valides en combinaison avec autre chose. Par exemple:

int foo;
...
float foo;

La déclaration int foo;en elle-même serait parfaitement bien. De même la déclaration float foo;. Certains compilateurs peuvent enregistrer le numéro de ligne où la première déclaration est apparue, et associer un message d'information à cette ligne, pour aider le programmeur à identifier les cas où la définition précédente est en fait la définition erronée. Les compilateurs peuvent également conserver les numéros de ligne associés à quelque chose comme a do, qui peut être signalé si le associé whilen'apparaît pas au bon endroit. Cependant, pour les cas où l'emplacement probable du problème précède immédiatement la ligne où l'erreur est découverte, les compilateurs ne prennent généralement pas la peine d'ajouter un rapport supplémentaire pour la position.

supercat
la source