2015-09-29 20 views
12

Hãy nói rằng tôi có hai tập tin, a.h:Có thể #endif trong một tệp được bao gồm để đóng #if trong tệp bao gồm không?

#if 1 
#include "b.h" 

b.h:

#endif 

Cả hai của gcc và preprocessors kêu vang của từ chối a.h:

$ cpp -ansi -pedantic a.h >/dev/null 
In file included from a.h:2:0: 
b.h:1:2: error: #endif without #if 
#endif 
^
a.h:1:0: error: unterminated #if 
#if 1 
^ 

Tuy nhiên, tiêu chuẩn C (N15706.10.2.3) cho biết:

Một chỉ thị tiền xử lý của mẫu

# include "q-char-sequence" new-line

gây ra thay thế chỉ thị rằng toàn bộ nội dung của tập tin nguồn được xác định theo trình tự quy định giữa các " delimiters.

xuất hiện để cho phép cấu trúc ở trên.

Gcc và clang có không tuân thủ trong việc từ chối mã của tôi không?

+2

Ngay cả khi đặt '# if' vào một tệp và' # endif' tương ứng trong một tệp khác là hợp pháp, IMHO sẽ là một ý tưởng tồi. –

Trả lời

12

Chuẩn C xác định 8 giai đoạn dịch. Tệp nguồn được xử lý theo từng giai đoạn trong chuỗi 8 (hoặc theo cách tương đương).

Giai đoạn 4, như được định nghĩa trong mục N1570 phần 5.1.1.2, là:

chỉ thị tiền xử lý được thực thi, lời gọi vĩ mô được mở rộng, và _Pragma biểu thức toán tử đơn hạng được thực thi. Nếu một chuỗi ký tự khớp với cú pháp của một ký tự phổ quát, tên được tạo ra bằng cách nối mã thông báo (6.10.3.3), hành vi là không xác định. Một #include chỉ thị tiền xử lý làm cho tệp tiêu đề hoặc tệp nguồn được đặt tên được xử lý từ giai đoạn 1 đến giai đoạn 4, đệ quy. Tất cả các chỉ thị tiền xử lý sau đó sẽ bị xóa.

Câu liên quan ở đây là:

Một #include chỉ thị tiền xử lý làm cho tên tiêu đề hoặc nguồn tập tin được xử lý từ giai đoạn 1 đến giai đoạn 4, đệ quy.

ngụ ý rằng mỗi tệp nguồn được bao gồm được tự xử lý trước. Điều này ngăn cản có một #if trong một tệp và #endif tương ứng trong một tệp khác.

(As "Một con voi hoang dã" được đề cập trong nhận xét và như rodrigo's answer nói, ngữ pháp trong phần 6.10 cũng nói rằng một phần if-, bắt đầu với một dòng #if (hoặc #ifdef hoặc #ifndef) và kết thúc với một dòng #endif, chỉ có thể xuất hiện như là một phần của preprocessing-file.)

+0

Tuyệt vời, cảm ơn bạn! Nếu bạn tò mò, điều gì làm tôi hỏi đây là đoạn văn này từ hướng dẫn sử dụng: https://twitter.com/whitequark/status/648870461514850305 – whitequark

+1

Để đặt nó theo cách khác 'mỗi tệp nguồn được bao gồm được xử lý thông qua giai đoạn dịch 4 trước tệp nguồn bao gồm có thể hoàn thành giai đoạn 4' – Les

-2
#if/#ifdef/#ifndef 
#elif 
#else 
#endif 

phải được khớp trong một tệp.

+5

Vì ngôn ngữ C được xác định theo tiêu chuẩn C, bạn có thể chỉ ra nơi chuẩn C yêu cầu điều này không? Tôi không thể tìm thấy một yêu cầu như vậy được nêu ở bất cứ đâu. – whitequark

+0

@whitequark Vâng quy tắc if-section của cú pháp được mô tả trong 6.10 yêu cầu điều này. –

+0

@Awildelephant bạn có thể chính xác hơn không? Phần 6.10.1.6 chỉ nói rằng (các) nhóm tương ứng bị bỏ qua; nó không đề cập đến các tập tin. Ví dụ. trong 'tất cả các nhóm cho đến khi #endif bị bỏ qua'. – whitequark

0

Suy nghĩ về bộ tiền xử lý C là trình biên dịch rất đơn giản, để dịch một tệp mà bộ xử lý tiền xử lý C thực hiện một vài giai đoạn.

  1. phân tích từ vựng - Nhóm chuỗi các ký tự tạo thành đơn vị tiền xử lý dịch thành chuỗi có một ý nghĩa xác định (tokens) trong ngôn ngữ tiền xử lý.
  2. Phân tích cú pháp - Nhóm các thẻ của đơn vị dịch tiền xử lý thành các cấu trúc cú pháp được xây dựng theo ngữ pháp tiền xử lý ngôn ngữ.
  3. Tạo mã - Dịch tất cả các tệp tạo thành đơn vị dịch tiền xử lý thành một tệp có chứa chỉ dẫn 'thuần' C.

Nói đúng ra, các giai đoạn dịch nêu tại §5.1.1.2 của C Standard (ISO/IEC 9899:201x) liên quan đến tiền xử lý là giai đoạn 3 và giai đoạn 4. Giai đoạn 3 tương ứng gần như chính xác để phân tích từ vựng trong khi giai đoạn 4 là về thế hệ mã.

Phân tích cú pháp (phân tích cú pháp) dường như bị thiếu trong ảnh đó. Thật vậy, ngữ pháp tiền xử lý C đơn giản đến nỗi các trình tiền xử lý/trình biên dịch thực sự thực hiện nó cùng với phân tích từ vựng.

Nếu giai đoạn phân tích cú pháp kết thúc thành công - tức là tất cả các câu lệnh trong đơn vị dịch tiền xử lý là hợp pháp theo ngữ pháp tiền xử lý - việc tạo mã có thể diễn ra và tất cả các chỉ thị tiền xử lý được thực hiện.
Thực thi một chỉ thị tiền xử lý có nghĩa là chuyển đổi tệp nguồn theo ngữ nghĩa của nó và sau đó loại bỏ chỉ thị khỏi tệp nguồn.
Ngữ nghĩa cho mỗi chỉ thị tiền xử lý được quy định trong §6.10.1-6.10.9 của tiêu chuẩn C.

Quay lại chương trình mẫu của bạn, 2 tệp bạn đã cung cấp, tức là a.hb.h, được xử lý theo khái niệm như sau.

Lexical Analysis - Mỗi tiền xử lý thẻ cá nhân được giới hạn bởi một '{' trên bên trái và một '}' ở bên phải.

a.h

{#}{if} {1} 
{#}{include} {"b.h"} 

b.h

{#}{endif} 

Giai đoạn này được thực hiện mà không có lỗi và kết quả của nó, trình tự các thẻ tiền xử lý, được chuyển sang giai đoạn tiếp theo: Phân tích cú pháp.

Phân tích cú pháp

Một nguồn gốc dự kiến ​​cho ah được đưa ra dưới đây

preprocessing-file → 
group → 
group-part → 
if-section → 
if-group endif-line → 
if-group #endif new-line → 
… 

và rõ ràng là nội dung của ah không thể được bắt nguồn từ ngữ pháp tiền xử lý - trong thực tế, chấm dứt #endif bị thiếu - và do đó a.h không chính xác về cú pháp. Đây chính là điều mà trình biên dịch của bạn cho bạn biết khi viết

a.h:1:0: error: unterminated #if 

Điều gì đó tương tự cũng xảy ra cho b.h; lý luận ngược, các #endif chỉ có thể xuất phát từ nguyên tắc

if-section → 
if-group elif-groups[opt] else-group[opt] endif-line 

Điều này có nghĩa các nội dung tập tin nên được bắt nguồn từ một trong 3 nhóm sau

# if constant-expression new-line group[opt] 
# ifdef identifier new-line group[opt] 
# ifndef identifier new-line group[opt] 

Vì nó không phải là trường hợp, bởi vì b.h không chứa # if/# ifdef/# ifndef nhưng chỉ có một dòng #endif, một lần nữa nội dung của b.h không chính xác về cú pháp và trình biên dịch của bạn cho bạn biết về cách này

In file included from a.h:2:0: 
b.h:1:2: error: #endif without #if 

Code Generation

Tất nhiên, kể từ khi chương trình của bạn là lexically âm thanh nhưng cú pháp không đúng, giai đoạn này không bao giờ được thực hiện.

7

Tôi nghĩ rằng trình biên dịch là đúng hoặc tốt nhất là tiêu chuẩn không rõ ràng.

Bí quyết không nằm trong cách thực hiện #include, nhưng theo thứ tự mà quá trình tiền xử lý được thực hiện.

Nhìn vào các quy tắc ngữ pháp trong phần 6.10 của tiêu chuẩn C99:

preprocessing-file: 
    group[opt] 

group: 
    group-part 
    group group-part 

group-part: 
    if-section 
    control-line 
    text-line 
    # non-directive 

if-section: 
    if-group elif-groups[opt] else-group[opt] endif-line 

if-group: 
    # if constant-expression new-line group[opt] 
... 
control-line: 
    # include pp-tokens new-line 
    ... 

Như bạn có thể thấy, những thứ #include được lồng bên trong group, và group là điều bên trong #if/#endif.

Ví dụ, trong một tập tin cũng như hình thành như:

#if 1 
#include <a.h> 
#endif 

Điều đó sẽ phân tích như #if 1, cộng với một group, cộng #endif. Và bên trong group có một số #include.

Nhưng trong ví dụ của bạn:

#if 1 
#include <a.h> 

Nguyên tắc if-section không áp dụng cho nguồn này, vì vậy group sản xuất thậm chí còn không được chọn.

Có thể bạn có thể cho rằng tiêu chuẩn không rõ ràng, vì nó không chỉ định khi thay thế chỉ thị #include xảy ra và thực hiện tuân thủ có thể thay đổi nhiều quy tắc ngữ pháp và thay thế #include trước khi không tìm thấy #endif. Nhưng những sự mơ hồ này là không thể tránh được nếu các tác dụng phụ của cú pháp sửa đổi văn bản bạn đang phân tích cú pháp. Không phải là C tuyệt vời sao?

+0

Ah, đây cũng là một điểm tốt. Ước gì tôi có thể chấp nhận hai câu trả lời! – whitequark

Các vấn đề liên quan