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.