2015-01-09 14 views
31

Cố gắng một số mã tôi nhận ra rằng đoạn mã sau biên dịch:Làm cách nào để trả về cấu trúc ẩn danh trong C?

struct { int x, y; } foo(void) { 
} 

Dường như nếu chúng ta định nghĩa một hàm có tên foo mà trả về một ẩn danh struct.

Bây giờ, câu hỏi của tôi là: Liệu nó chỉ xảy ra để biên dịch với trình biên dịch của tôi hoặc điều này là hợp pháp C (99)? Nếu vậy, cú pháp chính xác cho câu lệnh return là gì và làm thế nào tôi có thể gán đúng giá trị trả về cho một biến?

+0

Có vẻ như nó một số hack của gcc.With C99 nghiêm ngặt nó được đưa ra lỗi http://ideone.com/665vM2 – Ankur

+4

@Shan: Cú pháp định nghĩa hàm vẫn hợp pháp. Nó chỉ đơn thuần than phiền về câu lệnh trả về còn thiếu (và câu hỏi của tôi hỏi cách thực hiện một câu lệnh trả về đúng trong tình huống này). – Askaga

+0

Không có thứ gì như cấu trúc ẩn danh trong C99. Bạn có thể tạo _ literound literals_ nhưng chúng luôn có phạm vi cục bộ. – Lundin

Trả lời

23

Cấu trúc bạn đang trả về không phải là cấu trúc ẩn danh. C tiêu chuẩn định nghĩa một cấu trúc ẩn danh như là một thành viên của cấu trúc khác mà không sử dụng thẻ. Những gì bạn đang quay trở lại là một cấu trúc không có thẻ, nhưng vì nó không phải là một thành viên, nó không phải là vô danh. Gcc sử dụng tên < ẩn danh> để biểu thị cấu trúc không có thẻ.

Giả sử bạn cố gắng khai báo cấu trúc giống hệt nhau trong hàm.

struct { int x, y; } foo(void) 
{ 
    return (struct { int x, y; }){ 0 } ; 
} 

gcc phàn nàn về nó: loại không tương thích khi trở về gõ 'struct < nặc danh>' nhưng 'struct < nặc danh>' đã được dự kiến ​​

Rõ ràng các loại không tương thích. Nhìn vào tiêu chuẩn chúng ta thấy rằng:

6.2.7 loại tương thích và loại composit

1: Có hai loại có loại tương thích nếu loại của họ đều giống nhau. Các quy tắc bổ sung để xác định xem hai loại có tương thích được mô tả trong 6.7.2 đối với các loại thông số, trong 6.7.3 đối với các loại định tính và trong 6.7.6 đối với người khai báo hay không. Ngoài ra, hai loại cấu trúc, công đoàn hoặc liệt kê được dịch riêng biệt đơn vị tương thích nếu thẻ và thành viên của chúng thỏa mãn các yêu cầu sau: Nếu thẻ được khai báo bằng thẻ, thẻ còn lại sẽ được khai báo với cùng một thẻ.Nếu cả hai được hoàn thành ở bất kỳ đâu trong đơn vị dịch tương ứng, thì các yêu cầu bổ sung sau đây áp dụng: sẽ có sự tương ứng một-một giữa các thành viên sao cho mỗi cặp thành viên tương ứng được khai báo với các loại tương thích; nếu một thành viên của cặp được khai báo với một bộ định danh căn chỉnh thì phần tử kia được khai báo với một bộ định tuyến căn chỉnh tương đương; và nếu một thành viên của cặp được khai báo với một tên, người kia được khai báo có cùng tên. Đối với hai cấu trúc, các thành viên tương ứng sẽ được khai báo theo cùng thứ tự. Đối với hai cấu trúc hoặc công đoàn, các trường bit tương ứng phải có cùng độ rộng. Đối với hai điều tra, các thành viên tương ứng sẽ có cùng giá trị.

Phần in đậm thứ hai, giải thích rằng nếu cả hai cấu trúc không có thẻ, chẳng hạn như trong ví dụ này, chúng phải tuân thủ các yêu cầu bổ sung được liệt kê sau phần đó. Nhưng nếu bạn nhận thấy phần in đậm đầu tiên, chúng phải nằm trong các đơn vị dịch riêng biệt, các cấu trúc trong ví dụ không phải là. Vì vậy, chúng không tương thích và mã không hợp lệ.

Nó là không thể làm cho mã chính xác vì nếu bạn khai báo một cấu trúc và sử dụng nó trong chức năng này, bạn phải sử dụng một thẻ, vi phạm các quy tắc mà cả hai đều có cấu trúc phải có thẻ giống nhau:

struct t { int x, y; } ; 

struct { int x, y; } foo(void) 
{ 
    struct t var = { 0 } ; 

return var ; 
} 

Again gcc phàn nàn: loại không tương thích khi trở về gõ 'struct t' nhưng 'struct < nặc danh>' đã được dự kiến ​​

+0

Rất tiếc, tôi chưa bao giờ đọc phần đó của Tiêu chuẩn trước đây. "Hai loại có kiểu tương thích nếu kiểu của chúng giống nhau" có vẻ hoàn toàn vô nghĩa đối với tôi (và nó có thể áp dụng cho tình huống này; ai biết), nhưng đó không phải là điều tôi muốn bình luận. Tôi có thể kết luận rằng mặc dù hai loại trong ví dụ của bạn không tương thích, chúng sẽ ** cả hai ** tương thích với một loại tương tự được định nghĩa trong một đơn vị dịch thuật khác? Và do đó khả năng tương thích không phải là một mối quan hệ chuyển tiếp? –

+0

@MarcvanLeeuwen Các loại phải giống nhau, mặc dù có rất nhiều ngoại lệ. Các đường dẫn không có thẻ sẽ tương thích nếu được định nghĩa trong các đơn vị dịch khác nhau. – 2501

8

Bạn có thể không rõ ràngreturn một số giá trị tổng hợp từ hàm của bạn (trừ khi bạn sử dụng tiện ích mở rộng typeof để nhận loại kết quả).

Các đạo đức của câu chuyện là ngay cả khi bạn có thể khai báo một hàm trả về một nặc danhstruct bạn nên thực tếkhông bao giờ làm điều đó.

Thay vào đó, đặt tên cho struct và mã:

struct twoints_st { int x; int y; }; 
struct twoints_st foo (void) { 
    return ((struct twoints_st) {2, 3}); 
}; 

Chú ý rằng nó là cú pháp ok, nhưng hành vi thường không xác định tại thực hiện, có một chức năng mà không return (ví dụ: bạn có thể gọi exit bên trong nó). Nhưng tại sao bạn muốn mã (có thể là hợp pháp):

struct { int xx; int yy; } bizarrefoo(void) { exit(EXIT_FAILURE); } 
12

này hoạt động trong phiên bản của tôi về GCC, nhưng có vẻ như một tổng hack. Có lẽ hữu ích trong mã được tạo tự động, nơi bạn không muốn xử lý sự phức tạp bổ sung của việc tạo các thẻ cấu trúc duy nhất, nhưng tôi sắp xếp kéo dài để đưa ra sự hợp lý hóa đó.

struct { int x,y; } 
foo(void) { 
    typeof(foo()) ret; 
    ret.x = 1; 
    ret.y = 10; 
    return ret; 
} 

main() 
{ 
    typeof(foo()) A; 

    A = foo(); 

    printf("%d %d\n", A.x, A.y); 
} 

Ngoài ra, nó phụ thuộc vào typeof() có mặt trong trình biên dịch - GCC và LLVM dường như để hỗ trợ nó, nhưng tôi chắc chắn nhiều trình biên dịch thì không.

+1

Kỹ thuật này có thể hợp lý hơn bạn nghĩ. Xem cách ngôn ngữ D có cú pháp thuận tiện cho nó: [https://dpaste.dzfl.pl/c5880e348812](https://dpaste.dzfl.pl/c5880e348812) – Cauterite

1

Điều này phù hợp với phiên bản GCC mới nhất. Nó đặc biệt hữu ích cho việc tạo mảng động với các macro. Ví dụ:

#define ARRAY_DECL(name, type) struct { int count; type *array; } name 

Sau đó, bạn có thể làm cho các mảng với realloc, vv Điều này rất hữu ích vì sau đó bạn có thể tạo một mảng động với bất kỳ loại, và chỉ có một cách để làm cho tất cả chúng. Nếu không, bạn sẽ kết thúc bằng cách sử dụng rất nhiều chức năng của void * và sau đó viết để thực sự có được các giá trị trở lại với phôi và như vậy. Bạn có thể tắt tất cả điều này bằng các macro; đó là vẻ đẹp của họ.

0

Hoặc bạn có thể tạo ra đệ quy vô hạn:

struct { int x, y; } foo(void) { 
    return foo(); 
} 

Mà tôi nghĩ là hoàn toàn hợp pháp.

+0

Làm thế nào về trả về một con trỏ đến kiểu cấu trúc? Trên lời gọi đầu tiên, malloc một số bộ nhớ và lưu trữ một con trỏ trong một void toàn cầu *. Sau đó, chỉ cần trả về void toàn cục *. Tôi nghĩ nếu một người đã thực sự có thể sử dụng loại một tuyên bố. – supercat

0

Dưới đây là một cách để trả về cấu trúc ẩn danh trong C++ 14 mà không có bất kỳ hack nào mà tôi vừa phát hiện.
(C++ 11 nên là đủ, tôi giả sử)
Trong trường hợp của tôi một hàm intersect() lợi nhuận std::pair<bool, Point> mà không phải là rất mô tả vì vậy tôi quyết định làm một kiểu tùy chỉnh cho kết quả.
Tôi có thể đã thực hiện riêng struct nhưng không đáng giá vì tôi chỉ cần nó cho trường hợp đặc biệt này; đó là lý do tại sao tôi sử dụng một cấu trúc ẩn danh.

auto intersect(...params...) { 
    struct 
    { 
     Point point; 
     bool intersects = false; 
    } result; 

    // do stuff... 

    return result; 
} 

Và bây giờ, thay vì xấu xí

if (intersection_result.first) { 
    Point p = intersection_result.second 

Tôi có thể sử dụng tốt hơn nhiều tìm kiếm:

if (intersection_result.intersects) { 
    Point p = intersection_result.point; 
+0

Câu hỏi là về C không phải C++. – Ruslan

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