Чому компілятор не повідомляє про відсутність крапки з комою?


115

У мене така проста програма:

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

Як видно, наприклад, 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, що саме є причиною цього та пов’язаними з ними помилками.


16
Що мотивувало цю публікацію?
R Sahu

10
@TavianBarnes Відкриття. Інше питання не можна виявити під час пошуку такого виду. Це могло б бути відредаговано таким чином, але це вимагатиме змінити трохи на багато, зробивши це зовсім іншим питанням IMO.
Якийсь програміст чувак

4
@TavianBarnes: Первісне питання просило помилку. Це питання задає питання, чому компілятор, здається, (принаймні, для ОП) неправильно повідомляє про місце помилки.
TonyK

80
Вказуйте на роздуми: якби компілятор міг систематично виявляти відсутні напівколонки, мові для початку не потрібні напівколонки.
Євро Міцеллі

5
Завдання компілятора - повідомити про помилку. Ваша робота - розібратися, що змінити, щоб виправити помилку.
Девід Шварц

Відповіді:


213

C - мова у вільній формі . Це означає, що ви можете її відформатувати багатьма способами, і це все ще буде легальною програмою.

Наприклад, твердження типу

a = b * c;

можна було написати так

a=b*c;

чи як

a
=
b
*
c
;

Тож коли компілятор бачить рядки

temp = *a
*a = *b;

вона думає, що це означає

temp = *a * a = *b;

Це, звичайно, не є дійсним виразом, і компілятор скаржиться на це замість відсутньої крапки з комою. Причина, яка не є дійсною, полягає в тому a, що це вказівник на структуру, тому *a * aнамагається помножити екземпляр структури ( *a) з вказівником на структуру ( a).

Хоча компілятор не може виявити відсутню крапку з комою, він також повідомляє про абсолютно непов’язану помилку в неправильній лінії. Це важливо помітити, оскільки скільки б ви не дивилися на рядок, де повідомляється про помилку, помилки там немає. Іноді подібні проблеми потребують того, щоб ви подивилися на попередні рядки, щоб побачити, чи добре вони і без помилок.

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

А іноді стає ще гірше: якщо ви включите два (або більше) файлів заголовків, а перший містить неповну декларацію, швидше за все, помилка синтаксису буде вказана у другому файлі заголовка.


У зв'язку з цим є концепція подальших помилок. Деякі помилки, як правило, через відсутність крапки з комою, повідомляються як кілька помилок. Ось чому важливо починати зверху під час виправлення помилок, оскільки виправлення першої помилки може призвести до зникнення декількох помилок.

Це, звичайно, може призвести до виправлення однієї помилки одночасно та частих перекомпіляцій, які можуть бути громіздкими при великих проектах. Визнання таких помилок у подальшому - це те, що має досвід, і після їх перегляду декілька разів легше розкопати реальні помилки та виправити більше однієї помилки за перекомпіляцію.


16
У C ++ temp = *a * a = *b може бути допустимим виразом, якби вони operator*були перевантажені. (Хоча питання позначено як "C".)
dan04

13
@ dan04: Якщо хтось насправді це зробив ... НАДЕ!
Кевін

2
+1 за порадою щодо (а) починаючи з першої повідомленої помилки; та (b) огляд назад, звідки повідомляється про помилку. Ви знаєте, що ви справжній програміст, коли ви автоматично дивитесь на рядок, перш ніж повідомляється про помилку :-)
TripeHound

@ TripeHound ОСОБЛИВО, коли є дуже велика кількість помилок або рядки, які раніше складені, викидають помилки ...
Tin Wizard

1
Як це зазвичай буває з метами
StoryTeller - Unslander Monica

27

Чому компілятор не виявить пропущену крапку з комою?

Три речі потрібно пам’ятати.

  1. Закінчення рядків у C - це просто звичайний пробіл.
  2. *в C може бути як одинарним, так і двійковим оператором. Як одинарний оператор, це означає «дереференція», як двійковий оператор означає «множити».
  3. Різниця між одинарними та бінарними операторами визначається з контексту, в якому вони бачать.

Результат цих двох фактів - це коли ми розбираємося.

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

Перший і останній *інтерпретуються як одинакові, а другі *трактуються як бінарні. З точки зору синтаксису це виглядає нормально.

Лише після розбору, коли компілятор намагається інтерпретувати оператори в контексті їх типів операндів, виявляється помилка.


4

Кілька гарних відповідей вище, але я докладно.

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>це дійсне твердження. Як було зазначено раніше, перший *відміняє вказівник, а другий помножує два значення.


3
Відвантаження має пріоритет, тому це (вміст адреси a) разів (вказівник на a). Ви можете сказати, оскільки помилка компіляції говорить про "недійсні операнди до двійкових * (мають" struct S "і" struct S * "), які є тими двома типами.
дасканді

Я кодую попередньо C99, так що жодних булів :-) Але ви добре зробите (+1), хоча порядок присвоєння насправді не був точкою моєї відповіді
Мауг каже, що поверніть Моніку

1
Але в цьому випадку yце навіть не змінна, це вираз *a *a, і ви не можете призначити результат множення.
Бармар

@Barmar, але компілятор не так далеко, він вже вирішив, що операнди до "бінарного *" є недійсними ще до того, як він перегляне оператора призначення.
підключення

3

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

int foo;
...
float foo;

Сама декларація int foo;була б чудово. Аналогічно декларація float foo;. Деякі компілятори можуть записати номер рядка, де з’явилася перша декларація, і пов’язати інформаційне повідомлення з цим рядком, щоб допомогти програмісту визначити випадки, коли попереднє визначення насправді є помилковим. Компілятори також можуть зберігати номери рядків, пов’язані з чимось на зразок а do, про що можна повідомити, якщо асоційований whileне з’явиться в потрібному місці. У випадках, коли ймовірне розташування проблеми буде безпосередньо перед рядком, де виявлена ​​помилка, однак компілятори, як правило, не намагаються додавати додатковий звіт для позиції.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.