Почему компилятор не сообщает об отсутствии точки с запятой?


у меня есть эта простая программа:

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

Как видно on например ideone.com это дает ошибку:

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

почему компилятор не обнаруживает пропущенную точку с запятой?


Примечание: этот вопрос и его ответ мотивированы этим вопросом. Хотя есть и другие вопросы, подобные этому, я не нашел ничего, что упоминало бы о способности свободной формы языка C, что является причиной этого и связано с этим ошибки.

4 105

4 ответа:

С -бесплатная форма язык. Это означает, что вы можете отформатировать его во многих отношениях, и это все равно будет законная программа.

например, такое утверждение, как

a = b * c;

может быть записан как

a=b*c;

или как

a
=
b
*
c
;

поэтому, когда компилятор видит строки

temp = *a
*a = *b;

он думает, что это означает

temp = *a * a = *b;

это, конечно, не является допустимым выражением, и компилятор будет жаловаться, что недостающей запятой. Почему это не действует потому что a - это указатель на структуру, так *a * a пытается умножить экземпляр структуры (*a) с указателем на структуру (a).

хотя компилятор не может обнаружить отсутствующую точку с запятой, он также сообщает о совершенно несвязанной ошибке в неправильной строке. Это важно заметить, потому что независимо от того, сколько вы смотрите на строку, где сообщается об ошибке, там нет ошибки. Иногда такие проблемы, как это нужно будет Вам посмотреть на линии, чтобы увидеть, если они в порядке и без ошибок.

иногда вам даже придется искать в другом файле, чтобы найти ошибку. Например, если файл заголовка определяет структуру последним, что он делает в файле заголовка, и точка с запятой, завершающая структуру, отсутствует, то ошибка будет не в файле заголовка, а в файле, который включает файл заголовка.

и иногда это становится еще хуже: при включении два (или более) заголовочных файла, и первый содержит неполное объявление, скорее всего, синтаксическая ошибка будет указана во втором заголовочном файле.


С этим связано понятие следовать-вверх ошибки. Некоторые ошибки, как правило, из-за отсутствия точек с запятой на самом деле, сообщаются как несколько ошибки. Вот почему важно начинать сверху при исправлении ошибок, так как исправление первой ошибки может привести к нескольким ошибкам исчезнуть.

это, конечно, может привести к исправлению одной ошибки за раз и частым перекомпиляциям, которые могут быть громоздкими с большими проектами. Распознавание таких последующих ошибок-это то, что приходит с опытом, хотя, увидев их несколько раз, легче выкопать реальные ошибки и исправить более одной ошибки на перекомпиляцию.

почему компилятор не обнаруживает пропущенную точку с запятой?

есть три вещи, чтобы помнить.

  1. концы строк в C-это просто обычные пробелы.
  2. * в C может быть как унарный и бинарный оператор. Как унарный оператор это означает "разыменование", как двоичный оператор это означает"умножение".
  3. разницу между унарных и бинарных операторов определяется из контекста, в котором они увиденный.

результат этих двух фактов, когда мы разбираем.

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

первый и последний * интерпретируются как унарные, но второй * интерпретируется как двоичный. С точки зрения синтаксиса, это выглядит нормально.

ошибка возникает только после синтаксического анализа, когда компилятор пытается интерпретировать операторы в контексте их типов операндов.

некоторые хорошие ответы выше, но я уточню.

temp = *a *a = *b;

это на самом деле случай x = y = z; когда как x и y присваивается значение z.

что это the contents of address (a times a) become equal to the contents of b, as does temp.

короче, *a *a = <any integer value> является допустимым заявление. Как указывалось ранее, первый * разыменовывает указатель, а второй умножает два значения.

большинство компиляторов разбирают исходные файлы по порядку и сообщают о строке, в которой они обнаруживают, что что-то не так. Первые 12 строк вашей программы C могут быть началом действительной (безошибочной) программы C. Первые 13 строк вашей программы не могут. Некоторые компиляторы будут отмечать расположение вещей, с которыми они сталкиваются, которые сами по себе не являются ошибками, и в большинстве случаев не будут вызывать ошибки позже в коде, но могут быть недействительными в сочетании с чем-то еще. Для пример:

int foo;
...
float foo;

декларации int foo; само по себе было бы прекрасно. Аналогично декларации float foo;. Некоторые компиляторы могут записать номер строки, где появилось первое объявление, и связать информационное сообщение с этой строкой, чтобы помочь программисту определить случаи, когда более раннее определение фактически является ошибочным. Компиляторы также могут сохранять номера строк, связанные с чем-то вроде do, который может быть сообщен, если связанный while не появляется в нужном месте. Однако в тех случаях, когда вероятное местоположение проблемы будет непосредственно предшествовать строке, в которой обнаружена ошибка, компиляторы обычно не утруждают себя добавлением дополнительного отчета для позиции.