¿Por qué el compilador no informa que falta un punto y coma?

115

Tengo este programa 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);
}

Como se ve en, por ejemplo, ideone.com, esto da un error:

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

¿Por qué el compilador no detecta el punto y coma que falta?


Nota: Esta pregunta y su respuesta están motivadas por esta pregunta . Si bien hay otras preguntas similares a esta, no encontré nada que mencione la capacidad de forma libre del lenguaje C, que es lo que está causando esto y los errores relacionados.

Algún tipo programador
fuente
16
¿Qué motivó esta publicación?
R Sahu
10
@TavianBarnes Discoverability. La otra pregunta no se puede descubrir al buscar este tipo de problema. Podría editarse de esa manera, pero eso requeriría cambiar un poco a mucho, lo que la convierte en una pregunta completamente diferente, en mi opinión.
Algún tipo programador
4
@TavianBarnes: La pregunta original pedía el error. Esta pregunta es preguntar por qué el compilador parece (al menos para el OP) estar informando erróneamente la ubicación del error.
TonyK
80
Punto para reflexionar: si un compilador pudiera detectar sistemáticamente los puntos y comas faltantes, el lenguaje no necesitaría punto y coma para empezar.
Euro Micelli
5
El trabajo de los compiladores es informar del error. Es su trabajo averiguar qué cambiar para corregir el error.
David Schwartz

Respuestas:

213

C es un lenguaje de forma libre . Eso significa que puede formatearlo de muchas maneras y seguirá siendo un programa legal.

Por ejemplo, una declaración como

a = b * c;

podría escribirse como

a=b*c;

o me gusta

a
=
b
*
c
;

Entonces, cuando el compilador vea las líneas

temp = *a
*a = *b;

piensa que significa

temp = *a * a = *b;

Por supuesto, esa no es una expresión válida y el compilador se quejará de eso en lugar del punto y coma que falta. La razón por la que no es válido es porque aes un puntero a una estructura, por lo que *a * aestá tratando de multiplicar una instancia de estructura ( *a) con un puntero a una estructura ( a).

Si bien el compilador no puede detectar el punto y coma que falta, también informa el error totalmente no relacionado en la línea incorrecta. Es importante notar esto porque no importa cuánto mire la línea donde se informa el error, no hay ningún error allí. A veces, problemas como este necesitarán que mire las líneas anteriores para ver si están bien y sin errores.

A veces, incluso tiene que buscar en otro archivo para encontrar el error. Por ejemplo, si un archivo de encabezado está definiendo una estructura lo último que lo hace en el archivo de encabezado, y falta el punto y coma que termina la estructura, entonces el error no estará en el archivo de encabezado sino en el archivo que incluye el archivo de encabezado.

Y a veces las cosas empeoran aún más: si incluye dos (o más) archivos de encabezado y el primero contiene una declaración incompleta, lo más probable es que el error de sintaxis se indique en el segundo archivo de encabezado.


Relacionado con esto está el concepto de errores de seguimiento . Algunos errores, generalmente debido a que faltan puntos y comas, se informan como errores múltiples . Por eso es importante comenzar desde arriba al corregir errores, ya que corregir el primer error puede hacer que desaparezcan varios errores.

Por supuesto, esto puede llevar a corregir un error a la vez y a recompilaciones frecuentes que pueden resultar engorrosas con proyectos grandes. Sin embargo, reconocer estos errores de seguimiento es algo que viene con la experiencia, y después de verlos varias veces, es más fácil descubrir los errores reales y corregir más de un error por recompilación.

Joachim Pileborg
fuente
16
En C ++, temp = *a * a = *b podría ser una expresión válida si operator*estuviera sobrecargado. (Sin embargo, la pregunta está etiquetada como "C".)
dan04
13
@ dan04: Si alguien realmente hizo eso ... ¡NOPE!
Kevin
2
+1 por el consejo sobre (a) comenzar con el primer error informado; y (b) mirar hacia atrás desde donde se informa el error. Sabes que eres un programador real cuando miras automáticamente en la línea anterior donde se informa un error :-)
TripeHound
@TripeHound ESPECIALMENTE cuando hay una gran cantidad de errores, o las líneas que compilaron previamente arrojan errores ...
Tin Wizard
1
Como suele ser el caso con meta, alguien ya preguntó: meta.stackoverflow.com/questions/266663/…
StoryTeller - Unslander Monica
27

¿Por qué el compilador no detecta el punto y coma que falta?

Hay tres cosas que recordar.

  1. Los finales de línea en C son espacios en blanco ordinarios.
  2. *en C puede ser un operador unario y binario. Como operador unario significa "desreferencia", como operador binario significa "multiplicar".
  3. La diferencia entre operadores unarios y binarios se determina a partir del contexto en el que se ven.

El resultado de estos dos hechos es cuando analizamos.

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

El primero y el último *se interpretan como unario, pero el segundo *se interpreta como binario. Desde una perspectiva de sintaxis, esto parece correcto.

Solo después de analizar cuando el compilador intenta interpretar los operadores en el contexto de sus tipos de operandos, se ve un error.

enchufar
fuente
4

Algunas buenas respuestas arriba, pero las detallaré.

temp = *a *a = *b;

Este es en realidad un caso x = y = z;en el que a ambos xy se yles asigna el valor de z.

Lo que estás diciendo es the contents of address (a times a) become equal to the contents of b, as does temp.

En resumen, *a *a = <any integer value>es una afirmación válida. Como se señaló anteriormente, el primero *elimina la referencia a un puntero, mientras que el segundo multiplica dos valores.

Mawg dice reinstalar a Monica
fuente
3
La desreferencia tiene prioridad, por lo que es (contenido de la dirección a) veces (puntero a a). Puede decir, porque el error de compilación dice "operandos no válidos para binario * (tienen 'estructura S' y 'estructura S *')", cuáles son esos dos tipos.
dascandy
Codifico pre C99, por lo que no hay bools :-) Pero tienes un buen punto (+1), aunque el orden de asignación no fue realmente el punto de mi respuesta
Mawg dice que restablezca a Monica el
1
Pero en este caso, yni siquiera es una variable, es la expresión *a *ay no se puede asignar al resultado de una multiplicación.
Barmar
@Barmar de hecho, pero el compilador no llega tan lejos, ya ha decidido que los operandos del "binario *" no son válidos antes de mirar el operador de asignación.
Plugwash
3

La mayoría de los compiladores analizan los archivos fuente en orden e informan la línea donde descubren que algo andaba mal. Las primeras 12 líneas de su programa C podrían ser el inicio de un programa C válido (sin errores). Las primeras 13 líneas de su programa no pueden. Algunos compiladores notarán la ubicación de las cosas que encuentran que no son errores en sí mismos y, en la mayoría de los casos, no desencadenarán errores más adelante en el código, pero es posible que no sean válidos en combinación con otra cosa. Por ejemplo:

int foo;
...
float foo;

La declaración int foo;por sí sola estaría perfectamente bien. Asimismo la declaración float foo;. Algunos compiladores pueden registrar el número de línea donde apareció la primera declaración y asociar un mensaje informativo con esa línea, para ayudar al programador a identificar los casos en los que la definición anterior es en realidad la errónea. Los compiladores también pueden mantener los números de línea asociados con algo como a do, que se puede informar si el asociado whileno aparece en el lugar correcto. Sin embargo, para los casos en los que la ubicación probable del problema sería inmediatamente anterior a la línea donde se descubre el error, los compiladores generalmente no se molestan en agregar un informe adicional para la posición.

Super gato
fuente