2013-05-30 36 views
15

Tôi vừa tìm thấy một quirk trong C mà tôi thấy thực sự khó hiểu. Trong C nó có thể sử dụng một con trỏ đến một cấu trúc trước khi nó đã được khai báo. Đây là một tính năng rất hữu ích có ý nghĩa bởi vì tuyên bố là không liên quan khi bạn chỉ xử lý với một con trỏ đến nó. Tôi chỉ tìm thấy một góc trường hợp mà điều này là (đáng ngạc nhiên) không đúng, mặc dù, và tôi không thể thực sự giải thích tại sao. Đối với tôi nó trông giống như một sai lầm trong thiết kế ngôn ngữ.Cảnh báo trình biên dịch kỳ lạ C: cảnh báo: ‘struct’ được khai báo bên trong danh sách tham số

Hãy mã này:

#include <stdio.h> 

#include <stdlib.h> 


typedef void (*a)(struct lol* etc); 

void a2(struct lol* etc) { 

} 

int main(void) { 
     return 0; 
} 

Cung cấp:

foo.c:6:26: warning: ‘struct lol’ declared inside parameter list [enabled by default] 
foo.c:6:26: warning: its scope is only this definition or declaration, which is probably not what you want [enabled by default] 
foo.c:8:16: warning: ‘struct lol’ declared inside parameter list [enabled by default] 

Để loại bỏ vấn đề này, chúng tôi chỉ có thể làm điều này:

#include <stdio.h> 

#include <stdlib.h> 

struct lol* wut; 

typedef void (*a)(struct lol* etc); 

void a2(struct lol* etc) { 

} 

int main(void) { 
     return 0; 
} 

Vấn đề không thể giải thích bây giờ là đi cho một không thể giải thích lý do. Tại sao?

Lưu ý rằng câu hỏi này là về hành vi của ngôn ngữ C (hoặc có thể là hành vi trình biên dịch của gcc và clang) và không phải ví dụ cụ thể mà tôi đã dán.

EDIT:

tôi sẽ không chấp nhận "trật tự kê khai là rất quan trọng" như một câu trả lời trừ khi bạn cũng giải thích tại sao C sẽ cảnh báo về việc sử dụng một con trỏ struct cho lần đầu tiên trong một danh sách đối số chức năng nhưng cho phép nó trong bất kỳ ngữ cảnh nào khác. Tại sao điều đó có thể là một vấn đề?

+2

Bạn vẫn nên nói rằng cấu trúc tồn tại trước bằng cách sử dụng 'struct lol; ' – Dave

+0

Trước tiên bạn phải thông báo cho trình biên dịch biết loại tồn tại như cấu trúc lol và sau đó bạn có thể sử dụng cấu trúc lo hoặc con trỏ đến cấu trúc trong tuyên bố của một chức năng mới – hetepeperfan

+2

Cũng có vẻ như trình biên dịch giải thích vấn đề chính xác cho bạn: phạm vi. Đọc các cảnh báo! – Dave

Trả lời

27

Để hiểu tại sao các biên dịch phàn nàn, bạn cần phải biết hai điều về C "struct" s:

  • họ được tạo ra (như là một tuyên bố, nhưng chưa được xác định, loại) càng sớm càng tốt tên họ, vì vậy sự xuất hiện đầu tiên của struct lol tạo ra một bản tuyên bố
  • họ tuân theo cùng một "phạm vi tuyên bố" quy tắc như các biến thông thường

(struct lol { tuyên bố và sau đó bắt đầu xác định cấu trúc, nó struct lol; hoặc struct lol * hoặc một thứ khác không có dấu ngoặc nhọn dừng sau bước "tuyên bố".)

Loại cấu trúc được khai báo nhưng chưa được xác định là trường hợp C gọi là "loại không đầy đủ" . Bạn được phép sử dụng con trỏ đến các loại không đầy đủ, miễn là bạn không cố gắng để làm theo con trỏ:

struct lol *global_p; 
void f(void) { 
    use0(global_p);  /* this is OK */ 
    use1(*global_p);  /* this is an error */ 
    use2(global_p->field); /* and so is this */ 
} 

Bạn phải hoàn thành các loại để "làm theo con trỏ", nói cách khác.

Trong mọi trường hợp, tuy nhiên, xem xét tờ khai chức năng với bình thường int thông số:

int imin2(int a, int b); /* returns a or b, whichever is smaller */ 
int isum2(int a, int b); /* returns a + b */ 

Biến tên ab đây được khai báo bên trong dấu ngoặc đơn, nhưng những tờ khai cần phải nhận được ra khỏi con đường để các tờ khai chức năng tiếp theo không khiếu nại về việc chúng được khai báo lại.

Điều tương tự cũng xảy ra với struct tag-tên:

void gronk(struct sttag *p); 

Các struct sttag khai báo một cấu trúc, và sau đó là tuyên bố được cuốn trôi, giống như những người cho ab. Nhưng điều đó tạo ra một vấn đề lớn: thẻ đã biến mất và bây giờ bạn không thể đặt tên cho loại cấu trúc này nữa! Nếu bạn viết:

struct sttag { int field1; char *field2; }; 

định nghĩa mới và khác nhau struct sttag, giống như:

void somefunc(int x) { int y; ... } 
int x, y; 

định nghĩa một mới và khác nhau xy ở phạm vi cấp độ file, khác nhau từ những người thân trong somefunc .

May mắn thay, nếu bạn khai báo (hoặc thậm chí xác định) các struct trước bạn viết phần khai báo hàm, khai báo nguyên mẫu cấp "đề cập trở lại" để khai báo bên ngoài-Phạm vi:

struct sttag; 
void gronk(struct sttag *p); 

Bây giờ cả hai struct sttag s là "giống nhau" struct sttag, vì vậy khi bạn hoàn thành struct sttag sau đó, bạn cũng đang hoàn thành một mẫu bên trong nguyên mẫu cho gronk.


Re câu hỏi chỉnh sửa: nó sẽ chắc chắn đã có thể để xác định tác động của cấu trúc, công đoàn, và các thẻ enum cách khác nhau, làm cho chúng "bong bóng ra khỏi" các nguyên mẫu để phạm vi bao quanh họ. Điều đó sẽ làm cho vấn đề biến mất. Nhưng nó không được định nghĩa theo cách đó. Vì đó là ủy ban ANSI C89 đã phát minh ra (hoặc lấy trộm, thực sự, từ nguyên mẫu C++), bạn có thể đổ lỗi cho chúng. :-)

+0

Cảm ơn. Tôi nghĩ đây là câu trả lời sâu sắc nhất. –

+2

Tôi đã xung quanh khi họ phát minh ra (phạm vi nguyên mẫu, và tất cả điều đó). Mọi người phát hiện ra vấn đề với các khai báo struct bên trong các prototype khi chúng bắt đầu sử dụng các prototype - thường nhận các thông báo lỗi không thể hiểu được từ các trình biên dịch đã kiểm tra kiểu, nhưng chỉ nói 'struct foo * không tương thích với struct foo *' mà không giải thích * tại sao *. Đó là ... thú vị. :-) – torek

5

Điều này là do, trong ví dụ đầu tiên, cấu trúc trước đây chưa được xác định và do đó trình biên dịch cố gắng xử lý tham chiếu đầu tiên này cho cấu trúc đó dưới dạng định nghĩa.

Nói chung, C là ngôn ngữ mà thứ tự khai báo của bạn quan trọng. Tất cả mọi thứ bạn sử dụng nên được khai báo đúng trong một số khả năng, do đó trình biên dịch có thể lý do về nó khi nó được tham chiếu trong ngữ cảnh khác.

Đây không phải là lỗi hoặc lỗi trong thiết kế ngôn ngữ. Thay vào đó, đó là một lựa chọn mà tôi tin rằng đã được thực hiện để đơn giản hóa việc triển khai các trình biên dịch C đầu tiên. Các khai báo chuyển tiếp cho phép một trình biên dịch dịch mã nguồn một cách thẳng thắn trong một lần truyền (miễn là một số thông tin như các kích thước và các offset được biết). Nếu đây không phải là trường hợp, trình biên dịch sẽ có thể đi qua lại trong chương trình bất cứ khi nào nó đáp ứng một định danh không được công nhận, yêu cầu vòng lặp phát xạ mã của nó phức tạp hơn nhiều.

+1

Về mặt kỹ thuật, bạn vẫn có thể làm tất cả trong "một lần", bạn chỉ cần giữ nhiều dữ liệu hơn trong bộ nhớ. Hoạt động tương tự về mặt đơn giản và phức tạp trong trình biên dịch, tất nhiên. :-) – torek

+0

@torek Đúng. Tôi sẽ xem liệu tôi có thể cụm từ chính xác hơn không. –

3

Trình biên dịch cảnh báo bạn về một tờ khai chuyển tiếp của struct lol. C cho phép bạn thực hiện việc này:

struct lol;  /* forward declaration, the size and members of 
        struct lol are unknown */ 

Điều này được sử dụng nhiều nhất khi xác định cấu trúc tự tham khảo, nhưng cũng hữu ích khi xác định cấu trúc riêng không bao giờ được xác định trong tiêu đề. Bởi vì trường hợp sử dụng sau này, nó được cho phép để khai báo chức năng mà nhận hoặc con trỏ trở về cấu trúc không đầy đủ:

void foo(struct lol *x); 

Tuy nhiên, chỉ cần sử dụng một struct chưa được khai báo trong phần khai báo hàm, như bạn đã làm, sẽ được hiểu là một địa phương tuyên bố không đầy đủ của struct lol phạm vi của nó bị ràng buộc với hàm.Cách giải thích này được bắt buộc bởi tiêu chuẩn C, nhưng nó không hữu ích (không có cách nào để xây dựng struct lol để truyền cho hàm này) và gần như chắc chắn không phải là những gì người lập trình dự định, do đó trình biên dịch cảnh báo.

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