Warum meldet der Compiler kein fehlendes Semikolon?

115

Ich habe dieses einfache Programm:

#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);
}

Wie auf zB ideone.com zu sehen, gibt dies einen Fehler:

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

Warum erkennt der Compiler das fehlende Semikolon nicht?


Hinweis: Diese Frage und ihre Antwort sind durch diese Frage motiviert . Obwohl es ähnliche Fragen gibt , habe ich nichts gefunden, was die Freiformkapazität der C-Sprache erwähnt, die diese und verwandte Fehler verursacht.

Ein Programmierer
quelle
16
Was hat diesen Beitrag motiviert?
R Sahu
10
@TavianBarnes Entdeckbarkeit. Die andere Frage ist bei der Suche nach solchen Problemen nicht erkennbar. Es könnte auf diese Weise bearbeitet werden, aber das würde ein wenig zu viel ändern erfordern, was es zu einer ganz anderen Frage macht, IMO.
Einige Programmierer Typ
4
@TavianBarnes: Die ursprüngliche Frage hat nach dem Fehler gefragt. Diese Frage stellt die Frage, warum der Compiler (zumindest für das OP) den Ort des Fehlers falsch zu melden scheint.
TonyK
80
Hinweis zum Nachdenken: Wenn ein Compiler systematisch fehlende Semikolons erkennen könnte, würde die Sprache zunächst keine Semikolons benötigen.
Euro Micelli
5
Die Aufgabe des Compilers besteht darin, den Fehler zu melden. Es ist Ihre Aufgabe, herauszufinden, was Sie ändern müssen, um den Fehler zu beheben.
David Schwartz

Antworten:

213

C ist eine Freiformsprache . Das heißt, Sie können es auf viele Arten formatieren und es wird immer noch ein legales Programm sein.

Zum Beispiel eine Aussage wie

a = b * c;

könnte wie geschrieben werden

a=b*c;

oder wie

a
=
b
*
c
;

Also, wenn der Compiler die Zeilen sieht

temp = *a
*a = *b;

es denkt, es bedeutet

temp = *a * a = *b;

Das ist natürlich kein gültiger Ausdruck und der Compiler wird sich darüber beschweren, anstatt das fehlende Semikolon. Der Grund, warum es nicht gültig ist, ist, dass aes sich um einen Zeiger auf eine Struktur handelt. Daher *a * awird versucht, eine Strukturinstanz ( *a) mit einem Zeiger auf eine Struktur ( a) zu multiplizieren .

Der Compiler kann das fehlende Semikolon zwar nicht erkennen, meldet aber auch den völlig unabhängigen Fehler in der falschen Zeile. Dies ist wichtig zu beachten, da unabhängig davon, wie oft Sie auf die Zeile schauen, in der der Fehler gemeldet wird, dort kein Fehler vorliegt. Manchmal müssen Sie bei solchen Problemen in den vorherigen Zeilen nachsehen , ob sie in Ordnung und fehlerfrei sind.

Manchmal müssen Sie sogar in einer anderen Datei suchen, um den Fehler zu finden. Wenn beispielsweise eine Header-Datei eine Struktur definiert, die zuletzt in der Header-Datei ausgeführt wurde, und das Semikolon zum Beenden der Struktur fehlt, liegt der Fehler nicht in der Header-Datei, sondern in der Datei, die die Header-Datei enthält.

Und manchmal wird es sogar noch schlimmer: Wenn Sie zwei (oder mehr) Header-Dateien einfügen und die erste eine unvollständige Deklaration enthält, wird der Syntaxfehler höchstwahrscheinlich in der zweiten Header-Datei angezeigt.


Damit verbunden ist das Konzept der Follow-up - Fehler. Einige Fehler, die normalerweise auf fehlende Semikolons zurückzuführen sind, werden als mehrere Fehler gemeldet . Aus diesem Grund ist es wichtig, beim Beheben von Fehlern von oben zu beginnen, da durch das Beheben des ersten Fehlers möglicherweise mehrere Fehler verschwinden.

Dies kann natürlich dazu führen, dass jeweils ein Fehler behoben wird und häufig neu kompiliert wird, was bei großen Projekten umständlich sein kann. Das Erkennen solcher Folgefehler ist jedoch mit Erfahrung verbunden, und nachdem Sie sie einige Male gesehen haben, ist es einfacher, die tatsächlichen Fehler herauszufinden und mehr als einen Fehler pro Neukompilierung zu beheben.

Joachim Pileborg
quelle
16
In C ++ temp = *a * a = *b könnte ein gültiger Ausdruck sein, wenn er operator*überladen wäre. (Die Frage ist jedoch als "C" markiert.)
dan04
13
@ dan04: Wenn jemand das tatsächlich getan hat ... NOPE!
Kevin
2
+1 für den Hinweis zu (a) beginnend mit dem ersten gemeldeten Fehler; und (b) Rückblick von wo der Fehler gemeldet wird. Sie wissen, dass Sie ein echter Programmierer sind, wenn Sie automatisch in die Zeile schauen, bevor ein Fehler gemeldet wird :-)
TripeHound
@TripeHound INSBESONDERE, wenn es eine sehr große Anzahl von Fehlern gibt oder Zeilen, die zuvor kompiliert wurden, Fehler auslösen ...
Tin Wizard
1
Wie es normalerweise bei Meta der Fall ist, hat bereits jemand gefragt - meta.stackoverflow.com/questions/266663/…
StoryTeller - Unslander Monica
27

Warum erkennt der Compiler das fehlende Semikolon nicht?

Es gibt drei Dinge zu beachten.

  1. Zeilenenden in C sind nur gewöhnliche Leerzeichen.
  2. *in C kann sowohl ein unärer als auch ein binärer Operator sein. Als unärer Operator bedeutet es "Dereferenzierung", als binärer Operator bedeutet er "multiplizieren".
  3. Der Unterschied zwischen unären und binären Operatoren wird aus dem Kontext bestimmt, in dem sie gesehen werden.

Das Ergebnis dieser beiden Tatsachen ist, wenn wir analysieren.

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

Der erste und der letzte *werden als unär interpretiert, der zweite *als binär. Aus syntaktischer Sicht sieht dies in Ordnung aus.

Erst nach dem Parsen, wenn der Compiler versucht, die Operatoren im Kontext ihrer Operandentypen zu interpretieren, wird ein Fehler angezeigt.

Plugwash
quelle
4

Einige gute Antworten oben, aber ich werde näher darauf eingehen.

temp = *a *a = *b;

Dies ist tatsächlich ein Fall, in x = y = z;dem beiden xund ydem Wert von zugewiesen wird z.

Was Sie sagen, ist the contents of address (a times a) become equal to the contents of b, as does temp.

Kurz gesagt, *a *a = <any integer value>ist eine gültige Aussage. Wie bereits erwähnt, *dereferenziert der erste einen Zeiger, während der zweite zwei Werte multipliziert.

Mawg sagt, Monica wieder einzusetzen
quelle
3
Die Dereferenzierung hat Priorität, also ist es (Inhalt der Adresse a) mal (Zeiger auf a). Sie können erkennen, dass der Kompilierungsfehler "ungültige Operanden für binäre * (haben 'Struktur S' und 'Struktur S *')" sind, die diese beiden Typen sind.
Dascandy
Ich codiere vor C99, also keine Bools :-) Aber du machst einen guten Punkt (+1), obwohl die Reihenfolge der Zuweisung nicht wirklich der Punkt meiner Antwort war
Mawg sagt, Monica
1
In diesem Fall yhandelt es sich jedoch nicht einmal um eine Variable, sondern um den Ausdruck *a *a, und Sie können dem Ergebnis einer Multiplikation keine Zuordnung zuweisen.
Barmar
@Barmar zwar, aber der Compiler kommt nicht so weit, er hat bereits entschieden, dass die Operanden für das "binäre *" ungültig sind, bevor er den Zuweisungsoperator betrachtet.
Plugwash
3

Die meisten Compiler analysieren Quelldateien der Reihe nach und melden die Zeile, in der sie feststellen, dass etwas nicht stimmt. Die ersten 12 Zeilen Ihres C-Programms können der Start eines gültigen (fehlerfreien) C-Programms sein. Die ersten 13 Zeilen Ihres Programms können nicht. Einige Compiler notieren den Ort von Dingen, auf die sie stoßen, die an und für sich keine Fehler sind, und lösen in den meisten Fällen später im Code keine Fehler aus, sind jedoch möglicherweise in Kombination mit etwas anderem nicht gültig. Beispielsweise:

int foo;
...
float foo;

Die Erklärung int foo;an sich wäre vollkommen in Ordnung. Ebenso die Erklärung float foo;. Einige Compiler zeichnen möglicherweise die Zeilennummer auf, in der die erste Deklaration angezeigt wurde, und ordnen dieser Zeile eine Informationsnachricht zu, um dem Programmierer zu helfen, Fälle zu identifizieren, in denen die frühere Definition tatsächlich die fehlerhafte ist. Compiler behalten möglicherweise auch die mit so etwas wie a verknüpften Zeilennummern bei do, die gemeldet werden können, wenn die zugehörigen whilenicht an der richtigen Stelle angezeigt werden . In Fällen, in denen der wahrscheinliche Ort des Problems unmittelbar vor der Zeile liegt, in der der Fehler entdeckt wird, müssen Compiler im Allgemeinen keinen zusätzlichen Bericht für die Position hinzufügen.

Superkatze
quelle