Tại sao trình biên dịch không báo thiếu dấu chấm phẩy?


115

Tôi có chương trình đơn giản này:

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

Như đã thấy trên ví dụ: Ideone.com, điều này gây ra lỗi:

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

Tại sao trình biên dịch không phát hiện dấu chấm phẩy bị thiếu?


Lưu ý: Câu hỏi này và câu trả lời của nó được thúc đẩy bởi câu hỏi này . Trong khi có những câu hỏi khác tương tự như vậy, tôi không tìm thấy bất cứ điều gì đề cập đến dung lượng dạng tự do của ngôn ngữ C là nguyên nhân gây ra lỗi này và các lỗi liên quan.


16
Điều gì đã thúc đẩy bài đăng này?
R Sahu

10
@TavianBarnes Khả năng khám phá. Câu hỏi khác không thể khám phá được khi tìm kiếm loại vấn đề này. Nó có thể được chỉnh sửa theo cách đó, nhưng điều đó sẽ yêu cầu thay đổi một chút đến nhiều, khiến nó trở thành một câu hỏi hoàn toàn khác IMO.
Một số lập trình viên dude

4
@TavianBarnes: Câu hỏi ban đầu là yêu cầu lỗi. Câu hỏi này đang đặt ra câu hỏi tại sao trình biên dịch dường như (ít nhất là với OP) đang báo cáo sai vị trí của lỗi.
TonyK

80
Điểm đáng suy ngẫm: nếu một trình biên dịch có thể phát hiện một cách có hệ thống các dấu chấm phẩy bị thiếu, thì ngôn ngữ sẽ không cần đến dấu chấm phẩy để bắt đầu.
Euro Micelli

5
Công việc của trình biên dịch là thông báo lỗi. Công việc của bạn là tìm ra những gì cần thay đổi để khắc phục lỗi.
David Schwartz,

Câu trả lời:


213

C là một ngôn ngữ dạng tự do . Điều đó có nghĩa là bạn có thể định dạng nó theo nhiều cách và nó vẫn sẽ là một chương trình hợp pháp.

Ví dụ một câu lệnh như

a = b * c;

có thể được viết như

a=b*c;

hoặc thích

a
=
b
*
c
;

Vì vậy, khi trình biên dịch thấy các dòng

temp = *a
*a = *b;

nó nghĩ nó có nghĩa là

temp = *a * a = *b;

Đó tất nhiên không phải là một biểu thức hợp lệ và trình biên dịch sẽ phàn nàn về điều đó thay vì dấu chấm phẩy bị thiếu. Lý do nó không hợp lệ là vì alà một con trỏ tới một cấu trúc, vì vậy *a * ađang cố gắng nhân một thể hiện cấu trúc ( *a) với một con trỏ đến một cấu trúc ( a).

Mặc dù trình biên dịch không thể phát hiện dấu chấm phẩy bị thiếu, nhưng nó cũng báo cáo lỗi hoàn toàn không liên quan trên dòng sai. Điều này rất quan trọng cần chú ý vì dù bạn có nhìn vào dòng báo lỗi bao nhiêu đi chăng nữa thì vẫn không có lỗi ở đó. Đôi khi những vấn đề như thế này bạn sẽ cần phải xem lại các dòng trước đó để xem chúng có ổn và không có lỗi hay không.

Đôi khi bạn thậm chí phải tìm trong một tệp khác để tìm ra lỗi. Ví dụ: nếu tệp tiêu đề đang xác định cấu trúc lần cuối cùng nó thực hiện trong tệp tiêu đề và dấu chấm phẩy kết thúc cấu trúc bị thiếu, thì lỗi sẽ không xảy ra trong tệp tiêu đề mà trong tệp bao gồm tệp tiêu đề.

Và đôi khi nó còn tồi tệ hơn: nếu bạn bao gồm hai (hoặc nhiều) tệp tiêu đề và tệp đầu tiên chứa một khai báo không đầy đủ, hầu hết có thể lỗi cú pháp sẽ được chỉ ra trong tệp tiêu đề thứ hai.


Liên quan đến vấn đề này là khái niệm về lỗi tiếp theo . Một số lỗi, thường do thiếu dấu chấm phẩy trên thực tế, được báo cáo là nhiều lỗi. Đây là lý do tại sao điều quan trọng là phải bắt đầu từ đầu khi sửa lỗi, vì việc sửa lỗi đầu tiên có thể làm cho nhiều lỗi biến mất.

Tất nhiên, điều này có thể dẫn đến việc sửa từng lỗi một và việc biên dịch lại thường xuyên có thể phức tạp với các dự án lớn. Tuy nhiên, nhận ra những lỗi tiếp theo như vậy là một điều gì đó đi kèm với kinh nghiệm và sau khi nhìn thấy chúng một vài lần, bạn sẽ dễ dàng tìm ra các lỗi thực sự và sửa nhiều lỗi cho mỗi lần biên dịch lại.


16
Trong C ++, temp = *a * a = *b có thể là một biểu thức hợp lệ nếu operator*được nạp chồng. (Câu hỏi đặt ra là đánh dấu là “C”, mặc dù.)
dan04

13
@ dan04: Nếu ai đó thực sự đã làm điều đó ... KHÔNG ĐƯỢC!
Kevin

2
+1 cho lời khuyên về (a) bắt đầu với lỗi được báo cáo đầu tiên; và (b) nhìn ngược lại nơi báo cáo lỗi. Bạn biết bạn là một người thực lập trình khi bạn tự động tìm kiếm trên dòng trước nơi một lỗi được báo cáo :-)
TripeHound

@TripeHound đặc biệt là khi có một số lượng rất lớn các sai sót, hoặc đường mà trước đây biên soạn được ném lỗi ...
Tín Hướng dẫn

1
Như thường là trường hợp với meta, ai đó đã hỏi - meta.stackoverflow.com/questions/266663/...
người kể chuyện - Unslander Monica

27

Tại sao trình biên dịch không phát hiện dấu chấm phẩy bị thiếu?

Có ba điều cần nhớ.

  1. Kết thúc dòng trong C chỉ là khoảng trắng thông thường.
  2. *trong C có thể vừa là toán tử một ngôi vừa là toán tử nhị phân. Là một toán tử một ngôi, nó có nghĩa là "tham chiếu", như một toán tử nhị phân, nó có nghĩa là "nhân".
  3. Sự khác biệt giữa toán tử đơn phân và nhị phân được xác định từ ngữ cảnh mà chúng được nhìn thấy.

Kết quả của hai dữ kiện này là khi chúng ta phân tích cú pháp.

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

Đầu tiên và cuối cùng *được hiểu là một ngôi nhưng thứ hai *được hiểu là nhị phân. Từ góc độ cú pháp, điều này có vẻ ổn.

Chỉ sau khi phân tích cú pháp khi trình biên dịch cố gắng giải thích các toán tử trong ngữ cảnh của các loại toán hạng của chúng thì lỗi mới được nhìn thấy.


4

Một số câu trả lời tốt ở trên, nhưng tôi sẽ nói rõ hơn.

temp = *a *a = *b;

Đây thực sự là một trường hợp trong x = y = z;đó cả hai xyđược gán giá trị của z.

Những gì bạn đang nói là the contents of address (a times a) become equal to the contents of b, as does temp.

Trong ngắn hạn, *a *a = <any integer value>là một tuyên bố hợp lệ. Như đã chỉ ra trước đó, giá trị đầu tiên bỏ qua *một con trỏ, trong khi giá trị thứ hai nhân hai giá trị.


3
Tham chiếu được ưu tiên, vì vậy (nội dung của địa chỉ a) lần (con trỏ tới a). Bạn có thể biết, vì lỗi biên dịch cho biết "toán hạng không hợp lệ thành nhị phân * (có 'struct S' và 'struct S *')" là hai loại đó.
dascandy

Tôi đang pre C99, vì vậy không bools :-) Nhưng bạn làm làm cho một điểm tốt (+1), mặc dù thứ tự của phân công là không thực sự là điểm câu trả lời của tôi
Mawg nói Khôi phục Monica

1
Nhưng trong trường hợp này, ythậm chí không phải là một biến, nó là một biểu thức *a *avà bạn không thể gán cho kết quả của một phép nhân.
Barmar

@Barmar thực sự nhưng trình biên dịch không đi được xa như vậy, nó đã quyết định rằng các toán hạng cho "nhị phân *" là không hợp lệ trước khi nó xem xét toán tử gán.
plugwash

3

Hầu hết các trình biên dịch phân tích cú pháp các tệp nguồn theo thứ tự và báo cáo dòng mà họ phát hiện ra rằng có điều gì đó không ổn. 12 dòng đầu tiên của chương trình C của bạn có thể là phần bắt đầu của một chương trình C hợp lệ (không có lỗi). 13 dòng đầu tiên của chương trình của bạn không thể. Một số trình biên dịch sẽ lưu ý vị trí của những thứ mà họ gặp phải không phải là lỗi và trong hầu hết các trường hợp sẽ không kích hoạt lỗi sau này trong mã, nhưng có thể không hợp lệ khi kết hợp với thứ khác. Ví dụ:

int foo;
...
float foo;

Tuyên bố int foo;tự nó sẽ hoàn toàn ổn. Tương tự như vậy khai báo float foo;. Một số trình biên dịch có thể ghi lại số dòng nơi khai báo đầu tiên xuất hiện và kết hợp một thông báo thông tin với dòng đó, để giúp lập trình viên xác định các trường hợp mà định nghĩa trước đó thực sự là định nghĩa sai. Các trình biên dịch cũng có thể giữ các số dòng được liên kết với một cái gì đó như a do, có thể được báo cáo nếu số được liên kết whilekhông xuất hiện ở đúng vị trí. Tuy nhiên, đối với các trường hợp vị trí có khả năng xảy ra sự cố sẽ nằm ngay trước dòng nơi phát hiện ra lỗi, các trình biên dịch thường không bận tâm thêm một báo cáo bổ sung cho vị trí.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.