Perché il compilatore non segnala un punto e virgola mancante?


115

Ho questo semplice programma:

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

Come visto ad esempio su ideone.com questo dà un errore:

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

Perché il compilatore non rileva il punto e virgola mancante?


Nota: questa domanda e la sua risposta sono motivate da questa domanda . Sebbene ci siano altre domande simili a questa, non ho trovato nulla che menzioni la capacità del formato libero del linguaggio C che è ciò che causa questo e gli errori correlati.


16
Cosa ha motivato questo post?
R Sahu

10
@TavianBarnes Discoverability. L'altra domanda non è rilevabile durante la ricerca di questo tipo di problema. Potrebbe essere modificato in questo modo, ma ciò richiederebbe un cambiamento un po 'troppo, rendendolo una domanda completamente diversa IMO.
Un tizio programmatore

4
@TavianBarnes: la domanda originale chiedeva l'errore. Questa domanda sta chiedendo perché il compilatore sembra (almeno all'OP) riportare erroneamente la posizione dell'errore.
TonyK

80
Punto su cui riflettere: se un compilatore potesse rilevare sistematicamente i punti e virgola mancanti, la lingua non avrebbe bisogno dei punti e virgola per cominciare.
Euro Micelli

5
Il compito dei compilatori è segnalare l'errore. Sta a te capire cosa cambiare per correggere l'errore.
David Schwartz

Risposte:


213

C è un linguaggio in formato libero . Ciò significa che puoi formattarlo in molti modi e sarà comunque un programma legale.

Ad esempio una dichiarazione come

a = b * c;

potrebbe essere scritto come

a=b*c;

o simili

a
=
b
*
c
;

Quindi, quando il compilatore vede le righe

temp = *a
*a = *b;

pensa che significhi

temp = *a * a = *b;

Ovviamente questa non è un'espressione valida e il compilatore se ne lamenterà invece del punto e virgola mancante. Il motivo per cui non è valido è perché aè un puntatore a una struttura, quindi *a * asta tentando di moltiplicare un'istanza di struttura ( *a) con un puntatore a una struttura ( a).

Sebbene il compilatore non sia in grado di rilevare il punto e virgola mancante, segnala anche l'errore totalmente non correlato sulla riga sbagliata. Questo è importante da notare perché non importa quanto guardi la riga in cui viene segnalato l'errore, non ci sono errori lì. A volte problemi come questo richiederanno che tu guardi le righe precedenti per vedere se sono a posto e senza errori.

A volte devi anche cercare in un altro file per trovare l'errore. Ad esempio, se un file di intestazione definisce una struttura l'ultima volta nel file di intestazione e manca il punto e virgola che termina la struttura, l'errore non sarà nel file di intestazione ma nel file che include il file di intestazione.

E a volte la situazione peggiora: se includi due (o più) file di intestazione e il primo contiene una dichiarazione incompleta, molto probabilmente l'errore di sintassi verrà indicato nel secondo file di intestazione.


Collegato a questo è il concetto di errori di follow-up . Alcuni errori, in genere dovuti a punti e virgola mancanti, vengono segnalati come errori multipli . Questo è il motivo per cui è importante iniziare dall'alto quando si correggono gli errori, poiché correggere il primo errore potrebbe far scomparire più errori.

Questo ovviamente può portare a correggere un errore alla volta e frequenti ricompilazioni che possono essere complicate con progetti di grandi dimensioni. Riconoscere tali errori di follow-up è qualcosa che viene fornito con l'esperienza, e dopo averli visti alcune volte è più facile scovare gli errori reali e correggere più di un errore per ricompilazione.


16
In C ++, temp = *a * a = *b potrebbe essere un'espressione valida se operator*fosse sovraccaricata. (La domanda è contrassegnata come "C", però.)
dan04

13
@ dan04: Se qualcuno lo ha fatto davvero ... NOPE!
Kevin

2
+1 per il consiglio su (a) iniziare con il primo errore segnalato; e (b) guardare indietro da dove è stato segnalato l'errore. Sai di essere un vero programmatore quando guardi automaticamente sulla riga prima dove viene segnalato un errore :-)
TripeHound

@TripeHound SOPRATTUTTO quando c'è un numero molto elevato di errori, o le righe che precedentemente compilate stanno generando errori ...
Tin Wizard

1
Come avviene di solito con la meta, qualcuno già chiesto - meta.stackoverflow.com/questions/266663/...
Storyteller - Unslander Monica

27

Perché il compilatore non rileva il punto e virgola mancante?

Ci sono tre cose da ricordare.

  1. Le terminazioni di riga in C sono solo normali spazi bianchi.
  2. *in C può essere sia un operatore unario che binario. Come operatore unario significa "dereferenziazione", come operatore binario significa "moltiplicare".
  3. La differenza tra operatori unari e binari è determinata dal contesto in cui vengono visualizzati.

Il risultato di questi due fatti è quando analizziamo.

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

Il primo e l'ultimo *vengono interpretati come unari, ma il secondo *viene interpretato come binario. Dal punto di vista della sintassi, sembra OK.

È solo dopo l'analisi, quando il compilatore cerca di interpretare gli operatori nel contesto dei loro tipi di operando, che viene visualizzato un errore.


4

Alcune buone risposte sopra, ma elaborerò.

temp = *a *a = *b;

Questo è in realtà un caso in x = y = z;cui sia xe yviene assegnato il valore di z.

Quello che stai dicendo è the contents of address (a times a) become equal to the contents of b, as does temp.

Insomma, *a *a = <any integer value>è un'affermazione valida. Come accennato in precedenza, il primo *dereferenzia un puntatore, mentre il secondo moltiplica due valori.


3
La dereferenziazione ha la priorità, quindi è (contenuto dell'indirizzo a) volte (puntatore ad a). Puoi dirlo, perché l'errore di compilazione dice "operandi non validi a binary * (have 'struct S' e 'struct S *')" che sono questi due tipi.
dascandy

Codice prima di C99, quindi niente bool :-) Ma tu fai un buon punto (+1), anche se l'ordine di assegnazione non era proprio il punto della mia risposta
Mawg dice reintegrare Monica il

1
Ma in questo caso, ynon è nemmeno una variabile, è l'espressione *a *ae non puoi assegnarla al risultato di una moltiplicazione.
Barmar

@Barmar infatti, ma il compilatore non arriva così lontano, ha già deciso che gli operandi del "binario *" non sono validi prima di esaminare l'operatore di assegnazione.
plugwash

3

La maggior parte dei compilatori analizza i file sorgente in ordine e riporta la riga in cui scoprono che qualcosa non andava. Le prime 12 righe del programma C potrebbero essere l'inizio di un programma C valido (privo di errori). Le prime 13 righe del tuo programma non possono. Alcuni compilatori noteranno la posizione delle cose che incontrano che non sono errori in sé e per sé e nella maggior parte dei casi non innescheranno errori più avanti nel codice, ma potrebbero non essere validi in combinazione con qualcos'altro. Per esempio:

int foo;
...
float foo;

La dichiarazione int foo;da sola andrebbe benissimo. Allo stesso modo la dichiarazione float foo;. Alcuni compilatori possono registrare il numero di riga in cui è apparsa la prima dichiarazione e associare un messaggio informativo a quella riga, per aiutare il programmatore a identificare i casi in cui la definizione precedente è effettivamente quella errata. I compilatori possono anche mantenere i numeri di riga associati a qualcosa di simile a do, che può essere segnalato se l'associato whilenon appare nel posto giusto. Tuttavia, per i casi in cui la probabile posizione del problema precede immediatamente la riga in cui viene rilevato l'errore, i compilatori generalmente non si preoccupano di aggiungere un rapporto extra per la posizione.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.