2012-05-01 36 views
40

Câu hỏi này không quá cụ thể; nó thực sự cho C làm giàu của riêng tôi và tôi hy vọng những người khác có thể tìm thấy nó hữu ích là tốt.Các chức năng ẩn danh bằng cách sử dụng các biểu thức câu lệnh GCC

Tuyên bố từ chối trách nhiệm: Tôi biết nhiều người sẽ có xung để phản hồi "nếu bạn đang cố gắng thực hiện FP thì chỉ cần sử dụng ngôn ngữ chức năng". Tôi làm việc trong một môi trường nhúng mà cần phải liên kết với nhiều thư viện C khác, và không có nhiều không gian cho nhiều libs chia sẻ lớn hơn và không hỗ trợ nhiều thời gian chạy ngôn ngữ. Hơn nữa, phân bổ bộ nhớ động là ra khỏi câu hỏi. Tôi cũng thực sự tò mò.

Nhiều người trong số chúng ta đã thấy điều này vĩ mô tiện lợi C cho các biểu thức lambda:

#define lambda(return_type, function_body) \ 
({ \ 
     return_type __fn__ function_body \ 
      __fn__; \ 
}) 

Và một cách sử dụng ví dụ là:

int (*max)(int, int) = lambda (int, (int x, int y) { return x > y ? x : y; }); 
max(4, 5); // Example 

Sử dụng gcc -std=c89 -E test.c, lambda mở rộng để:

int (*max)(int, int) = ({ int __fn__ (int x, int y) { return x > y ? x : y; } __fn__; }); 

Vì vậy, đây là những câu hỏi của tôi:

  1. Chính xác thì dòng int (* X) là gì; khai báo? Tất nhiên, int * X; là một con trỏ trỏ đến số nguyên, nhưng hai số này khác nhau như thế nào?

  2. Nhìn vào macro được trừ, cái gì cuối cùng làm __fn__ làm gì? Nếu tôi viết hàm kiểm tra void test() { printf("hello"); } test; - tức thì sẽ phát ra lỗi. Tôi không hiểu cú pháp đó.

  3. Điều này có ý nghĩa gì đối với việc gỡ lỗi? (Tôi đang có kế hoạch thử nghiệm bản thân mình với điều này và gdb, nhưng kinh nghiệm hoặc ý kiến ​​của người khác sẽ là tuyệt vời). Điều này sẽ vít lên phân tích tĩnh?

+3

Đây không phải là ANSI C. –

+0

Ngoài ra, tôi không thấy 'int (* X);' ở bất kỳ đâu. –

+0

Nó không có trong đó như vậy. Trong khi cố gắng tìm ra cú pháp, tôi hơi bối rối rằng int (* X); biên dịch, nhưng không thực sự chắc chắn những gì nó được xác định .. –

Trả lời

31

khai này (ở phạm vi khối):

int (*max)(int, int) = 
    ({ 
    int __fn__ (int x, int y) { return x > y ? x : y; } 
    __fn__; 
    }); 

không phải là C nhưng có giá trị GNU C.

Nó làm cho sử dụng hai gcc phần mở rộng:

  1. nested functions
  2. statement expressions

Cả chức năng lồng nhau (định nghĩa một hàm bên trong một câu lệnh ghép) và tuyên bố biểu (({}) , về cơ bản là một khối mang lại giá trị) không được phép trong C và com e từ GNU C.

Trong biểu thức câu lệnh, câu lệnh biểu thức cuối cùng là giá trị của cấu trúc. Đây là lý do tại sao hàm lồng nhau __fn__ xuất hiện dưới dạng câu lệnh biểu thức ở cuối biểu thức câu lệnh. Một hàm thiết kế (__fn__ trong câu lệnh biểu thức cuối cùng) trong một biểu thức được chuyển đổi thành một con trỏ trỏ đến một hàm bằng các chuyển đổi thông thường. Đây là giá trị được sử dụng để khởi tạo con trỏ hàm max.

+3

Như một chức năng hữu ích, Khi nào điều này sẽ được thực hiện vào ANSI C? – Pacerier

+1

Đó là cú pháp GNU C hợp lệ, nhưng nó có thực sự hợp lệ về mặt ngữ nghĩa không? Khi khối có chứa các định nghĩa hàm thoát (trước khi bất kỳ việc sử dụng thực tế nào của "biểu thức lambda" có thể xảy ra), thì trampoline chức năng có bị vô hiệu hóa cùng với nó không? – Dolda2000

+0

@ Dolda2000 Dữ liệu được tạo trong biểu thức câu lệnh có thể có thời lượng tĩnh. Sau khi tất cả, nó không còn ANSI C. Đó là GNU C. GNU xác định tiêu chuẩn đó. –

0
  1. int (*max)(int, int) là loại biến bạn đang khai báo. Nó được định nghĩa là một con trỏ hàm có tên là max, trả về int và lấy hai tham số int làm tham số.

  2. __fn__ đề cập đến tên hàm, trong trường hợp này là CPC

  3. Tôi không có câu trả lời ở đó. Tôi sẽ tưởng tượng bạn có thể bước qua nó nếu bạn đã chạy nó thông qua bộ tiền xử lý.

+0

wrt 2, tôi chỉ không hiểu tại sao 'int (* max) (int, int) = ({int __fn__ (int x, int y) {return x> y? x : y;} __fn__;}); 'biên dịch, nhưng' void test() {puts ("hello"); } kiểm tra; '* không * biên dịch? –

+0

@ B.VB. Tôi sẽ làm cho mã trông giống hệt nhau. 'void (* test)() = ({void test() {puts (" hello ");} test;});' hoặc nếu nó không hoạt động, 'void (* test)() = ({ void __fn __() {puts ("hello");} __fn__;}); ' – gcochard

+0

' __fn__' chỉ là một tên tùy ý cho hàm được xác định bên trong khối. Việc đặt tên không may là khó hiểu. – FooF

0

Câu trả lời một phần: Nó không phải là int (* X) mà bạn quan tâm. Đó là int (* X) (y, z). Đó là một con trỏ hàm với hàm X được gọi là (y, z) và trả về int.

Để gỡ lỗi, điều này sẽ thực sự khó khăn. Hầu hết các trình gỡ rối không thể theo dõi qua macro. Bạn rất có thể sẽ phải gỡ lỗi lắp ráp.

5

Macro lambda của bạn khai thác hai tính năng thú vị. Đầu tiên nó sử dụng các hàm lồng nhau để thực tế xác định phần thân của hàm (do đó lambda của bạn không thực sự ẩn danh, nó chỉ sử dụng biến số __fn__ tiềm ẩn (cần được đổi tên thành một cái gì đó khác). trình biên dịch, vì vậy có lẽ cái gì đó như yourapp__fn__ sẽ tốt hơn)

Tất cả điều này được tự thực hiện trong vòng một câu lệnh ghép GCC (xem http://gcc.gnu.org/onlinedocs/gcc/Statement-Exprs.html#Statement-Exprs), định dạng cơ bản trong số đó đi một cái gì đó như:.

({ ...; retval; }) 

sự câu lệnh cuối cùng của câu lệnh ghép là địa chỉ của hàm vừa khai báo. Bây giờ, int (*max)(int,int) chỉ đơn giản được gán giá trị của câu lệnh ghép, mà bây giờ là con trỏ tới hàm 'nặc danh' vừa được khai báo.

Macro gỡ lỗi là một nỗi đau của hoàng gia. Vì lý do tại sao test; .. ít nhất là ở đây, tôi nhận được 'thử nghiệm redeclared như loại khác nhau của biểu tượng', mà tôi giả định có nghĩa là GCC là xử lý nó như là một tuyên bố và không phải là một (vô dụng) biểu hiện. Bởi vì các biến không định kiểu mặc định là int và bởi vì bạn đã khai báo test là một hàm (về cơ bản là void (*)(void)) bạn nhận được điều đó .. nhưng tôi có thể sai về điều đó.

Điều này không thể di chuyển bằng bất kỳ khoảng trí tưởng tượng nào.

+0

+1 cho đề xuất đổi tên '__fn__'. – FooF

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