2010-05-24 36 views
8

Là một phần của việc trả lời một câu hỏi khác, tôi đã xem một đoạn mã như thế này, mà gcc biên dịch mà không có khiếu nại.Làm cách nào để hợp pháp tham chiếu một loại không xác định bên trong một cấu trúc?

typedef struct { 
    struct xyz *z; 
} xyz; 
int main (void) { 
    return 0; 
} 

Đây là phương tiện Tôi luôn sử dụng để xây dựng các loại trỏ đến bản thân (ví dụ, danh sách liên kết) nhưng tôi đã luôn luôn nghĩ rằng bạn phải tên struct, do đó bạn có thể sử dụng tự tham khảo . Nói cách khác, bạn không thể sử dụng xyz *z trong cấu trúc vì typedef chưa hoàn thành tại thời điểm đó.

Nhưng mẫu cụ thể này không không phải là đặt tên cấu trúc và nó vẫn biên dịch. Tôi nghĩ ban đầu có một số ma thuật đen đang diễn ra trong trình biên dịch tự động dịch mã trên bởi vì cấu trúc và tên typedef giống nhau.

Nhưng vẻ đẹp chút này làm việc cũng như:

typedef struct { 
    struct NOTHING_LIKE_xyz *z; 
} xyz; 

tôi thiếu gì ở đây? Điều này có vẻ vi phạm rõ ràng vì không có loại struct NOTHING_LIKE_xyz được xác định ở bất kỳ đâu.

Khi tôi thay đổi nó từ một con trỏ tới một kiểu thực tế, tôi nhận được lỗi dự kiến:

typedef struct { 
    struct NOTHING_LIKE_xyz z; 
} xyz; 

qqq.c:2: error: field `z' has incomplete type 

Ngoài ra, khi tôi loại bỏ các struct, tôi nhận được một lỗi (parse error before "NOTHING ...).

Điều này có được phép trong ISO C không?


Cập nhật: Một struct NOSUCHTYPE *variable; cũng biên dịch vì vậy nó không chỉ bên cấu trúc nơi nó có vẻ là hợp lệ. Tôi không thể tìm thấy bất cứ điều gì trong tiêu chuẩn c99 cho phép khoan dung này cho con trỏ cấu trúc.

+1

Khối mã thứ hai và thứ ba giống hệt nhau. – badp

+0

Rất tiếc, đã khắc phục điều đó. Lỗi người dùng Cut'n'paste. – paxdiablo

+0

+1, Câu hỏi đó khiến nhiều người gãi đầu (bao gồm tôi). –

Trả lời

6

Các bộ phận của tiêu chuẩn C99 bạn đang sau là 6.7.2.3, đoạn 7:

If a type specifier of the form struct-or-union identifier occurs other than as part of one of the above forms, and no other declaration of the identifier as a tag is visible, then it declares an incomplete structure or union type, and declares the identifier as the tag of that type.

... và 6.2.5 đoạn 22:

A structure or union type of unknown content (as described in 6.7.2.3) is an incomplete type. It is completed, for all declarations of that type, by declaring the same structure or union tag with its defining content later in the same scope.

+0

Đó là những gì tôi muốn xem, là một luật sư ngôn ngữ có tính hậu môn :-) Mặc dù đó là bản para8 trong bản sao của tôi (nhưng tôi đã cập nhật lên TC3 để có thể giải thích điều đó). – paxdiablo

7

Khi cảnh báo cho biết trong trường hợp thứ hai, struct NOTHING_LIKE_xyzloại không đầy đủ, như void hoặc mảng có kích thước không xác định. Một kiểu không đầy đủ chỉ có thể xuất hiện như một loại được trỏ tới, với một ngoại lệ cho các mảng có kích thước không xác định được cho phép làm thành viên cuối cùng của một cấu trúc, làm cho cấu trúc trở thành một kiểu không hoàn chỉnh trong trường hợp này. Các mã sau đây không thể dereference bất kỳ con trỏ đến một loại không đầy đủ (vì lý do chính đáng).

Các loại không đầy đủ có thể cung cấp một số loại đóng gói dữ liệu trong các loại ... Đoạn tương ứng trong http://www.ibm.com/developerworks/library/pa-ctypes1/ có vẻ như là một lời giải thích tốt.

+0

+1 để giải thích và tham khảo tài liệu hỗ trợ. – paxdiablo

2

Trường hợp thứ nhất và thứ 2 được xác định rõ ràng, vì kích thước và căn chỉnh của con trỏ được biết. Trình biên dịch C chỉ cần kích thước và thông tin căn chỉnh để xác định cấu trúc.

Trường hợp thứ 3 không hợp lệ vì kích thước của cấu trúc thực tế đó không xác định.

Nhưng hãy cẩn thận rằng đối với trường hợp 1 là hợp lý, bạn cần phải đặt tên cho các cấu trúc:

//    vvv 
typedef struct xyz { 
    struct xyz *z; 
} xyz; 

nếu không thì ngoài struct và *z sẽ được xem xét hai cấu trúc khác nhau.


Trường hợp thứ 2 có trường hợp sử dụng phổ biến được gọi là "opaque pointer" (pimpl). Ví dụ, bạn có thể định nghĩa một struct wrapper như

typedef struct { 
    struct X_impl* impl; 
} X; 
// usually just: typedef struct X_impl* X; 
int baz(X x); 

trong tiêu đề, và sau đó trong một trong những .c,

#include "header.h" 
struct X_impl { 
    int foo; 
    int bar[123]; 
    ... 
}; 
int baz(X x) { 
    return x.impl->foo; 
} 

lợi thế là ra khỏi .c đó, bạn không thể gây rối với các internals của đối tượng. Nó là một loại đóng gói.

+0

+1 cho thông tin "cấu trúc thực sự khác". – paxdiablo

+0

Đồng ý, lời giải thích của bạn đã giúp tôi hiểu được lợi thế đóng gói. Bài viết wikipedia đó thực sự đã thúc đẩy ngôi nhà khái niệm! –

1

Bạn phải đặt tên cho nó. Trong này:

typedef struct { 
    struct xyz *z; 
} xyz; 

sẽ không thể để trỏ đến chính nó như là z đề cập đến một số hoàn chỉnh kiểu khác, không phải đến struct giấu tên bạn vừa xác định. Hãy thử điều này:

int main() 
{ 
    xyz me1; 
    xyz me2; 
    me1.z = &me2; // this will not compile 
} 

Bạn sẽ gặp lỗi về các loại không tương thích.

+0

Tôi nhận được một cảnh báo từ gcc (c chứ không phải là C++) nhưng +1 cho chỉ ra thực tế họ đang thực sự _different_ loại. – paxdiablo

0

Tôi cũng đã tự hỏi về điều này. Hóa ra rằng struct NOTHING_LIKE_xyz * z là chuyển tiếp tuyên bố struct NOTHING_LIKE_xyz. Như một ví dụ phức tạp,

typedef struct { 
    struct foo * bar; 
    int j; 
} foo; 

struct foo { 
    int i; 
}; 

void foobar(foo * f) 
{ 
    f->bar->i; 
    f->bar->j; 
} 

Đây f->bar đề cập đến loại struct foo, không typedef struct { ... } foo. Dòng đầu tiên sẽ biên dịch tốt, nhưng thứ hai sẽ đưa ra một lỗi. Không có nhiều sử dụng cho một danh sách liên kết thực hiện sau đó.

+0

Nó có thể được chuyển tiếp tuyên bố, hoặc struct foo có thể không được xác định ở tất cả trong đơn vị biên dịch, trong trường hợp đó là một loại không đầy đủ. –

1

Vâng ... Tất cả Tôi có thể nói là giả thiết trước đó của bạn là không chính xác. Mỗi khi bạn sử dụng một cấu trúc struct X (tự nó, hoặc như là một phần của khai báo lớn hơn), nó được hiểu là một khai báo kiểu cấu trúc với một thẻ cấu trúc X. Nó có thể là một tuyên bố lại của một kiểu cấu trúc đã khai báo trước đó. Hoặc, đây có thể là tuyên bố đầu tiên của loại cấu trúc mới mới. Thẻ mới được khai báo trong phạm vi mà thẻ xuất hiện. Trong ví dụ cụ thể của bạn, nó xảy ra là một phạm vi tệp (vì ngôn ngữ C không có "phạm vi lớp", vì nó sẽ có trong C++).

Ví dụ thú vị hơn của hành vi này là khi tờ khai xuất hiện trong chức năng nguyên mẫu:

void foo(struct X *p); // assuming `struct X` has not been declared before 

Trong trường hợp này struct X khai mới có hàm nguyên mẫu phạm vi, kết thúc vào cuối năm nguyên mẫu .Nếu bạn khai báo một file-phạm vi struct X sau

struct X; 

và cố gắng vượt qua một con trỏ của struct X loại để các chức năng trên, trình biên dịch sẽ cung cấp cho bạn một chẩn đoán về không phù hợp loại con trỏ

struct X *p = 0; 
foo(p); // different pointer types for argument and parameter 

Điều này cũng có nghĩa là ngay trong các tuyên bố sau đây

void foo(struct X *p); 
void bar(struct X *p); 
void baz(struct X *p); 

mỗi tuyên bố struct X là tuyên bố của khác nhau loại, mỗi địa phương để phạm vi nguyên mẫu chức năng riêng của mình.

Nhưng nếu bạn trước tuyên bố struct X như trong

struct X; 
void foo(struct X *p); 
void bar(struct X *p); 
void baz(struct X *p); 

tất cả các tài liệu tham khảo struct X trong tất cả các chức năng nguyên mẫu sẽ tham khảo cùng previosly tuyên bố struct X loại.

0

Khi biến hoặc trường của kiểu cấu trúc được khai báo, trình biên dịch phải phân bổ đủ byte để giữ cấu trúc đó. Vì cấu trúc có thể yêu cầu một byte, hoặc nó có thể yêu cầu hàng ngàn, không có cách nào để trình biên dịch biết được cần bao nhiêu không gian để phân bổ. Một số ngôn ngữ sử dụng nhiều trình biên dịch có thể tìm ra kích thước của cấu trúc trên một đường chuyền và phân bổ không gian cho nó trên một lần sau; vì C được thiết kế để cho phép biên dịch một lần, tuy nhiên, điều đó là không thể. Vì vậy, C cấm việc khai báo các biến hoặc các trường của các kiểu cấu trúc không đầy đủ.

Mặt khác, khi một biến hoặc trường của kiểu con trỏ tới cấu trúc được khai báo, trình biên dịch phải phân bổ đủ byte để giữ con trỏ đến cấu trúc. Bất kể cấu trúc có một byte hay một triệu, con trỏ sẽ luôn yêu cầu cùng một lượng không gian. Hiệu quả, trình biên dịch có thể tread con trỏ đến loại không đầy đủ như là một void * cho đến khi nó nhận được nhiều thông tin hơn về kiểu của nó, và sau đó coi nó như một con trỏ đến loại thích hợp khi nó tìm hiểu thêm về nó. Con trỏ kiểu không hoàn toàn không tương tự như void *, trong đó người ta có thể làm những thứ với void * mà người ta không thể làm với các kiểu không đầy đủ (ví dụ nếu p1 là một con trỏ đến struct s1, và p2 là một con trỏ đến struct s2, người ta không thể chỉ định p1 đến p2) nhưng người ta không thể làm bất cứ điều gì với một con trỏ đến một loại không đầy đủ mà người ta không thể làm để void *. Về cơ bản, từ quan điểm của trình biên dịch, một con trỏ tới một kiểu không đầy đủ là một chuỗi các byte có kích thước con trỏ. Nó có thể được sao chép vào hoặc từ các khối byte có kích thước con trỏ tương tự khác, nhưng đó là nó. trình biên dịch có thể tạo ra mã để làm điều đó mà không cần phải biết bất cứ điều gì khác sẽ làm với các đốm màu byte có kích thước con trỏ.

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