2015-05-29 14 views
12

Sau đây C# chức năng:Mục đích của ldnull và đuôi thêm là gì. trong F # thực hiện vs C#?

T ResultOfFunc<T>(Func<T> f) 
{ 
    return f(); 
} 

biên dịch gì ngạc nhiên như thế này:

IL_0000: ldarg.1  
IL_0001: callvirt 05 00 00 0A 
IL_0006: ret 

Nhưng tương đương với F # Chức năng:

let resultOfFunc func = func() 

biên dịch như sau:

IL_0000: nop   
IL_0001: ldarg.0  
IL_0002: ldnull  
IL_0003: tail.  
IL_0005: callvirt 04 00 00 0A 
IL_000A: ret 

(Cả hai đều ở chế độ phát hành). Có một nop thêm lúc đầu mà tôi không quá tò mò về, nhưng điều thú vị là thêm ldnulltail. hướng dẫn.

tôi đoán (có thể sai) là ldnull là cần thiết trong trường hợp hàm là void vì vậy nó vẫn trả về một cái gì đó (unit), nhưng điều đó không giải thích mục đích của hướng dẫn tail. là gì. Và điều gì sẽ xảy ra nếu chức năng đẩy thứ gì đó lên ngăn xếp, không phải là nó bị mắc kẹt với một giá trị rỗng không bị xuất hiện?

+3

Tôi nghi ngờ rằng trong trường hợp này hàm đã được hội tụ thành một cuộc gọi đuôi - chức năng mới sẽ sử dụng không gian ngăn xếp cũ –

+1

Lưu ý rằng điều này có khả năng gây ra bi quan hiệu suất, xem câu hỏi này: phạt khi Generic.List .Thêm là câu lệnh cuối cùng trong một chức năng và tối ưu hóa tailcall được bật] (http://stackoverflow.com/q/28649422/636019) – ildjarn

Trả lời

17

Phiên bản C# và F # có sự khác biệt quan trọng: Hàm C# không có bất kỳ tham số nào, nhưng phiên bản F # có một tham số kiểu unit. Giá trị unit là giá trị hiển thị là ldnull (vì null đang được sử dụng làm đại diện cho chỉ unit giá trị, ()).

Nếu bạn đã dịch các chức năng thứ hai để C#, nó sẽ trông như thế này:

T ResultOfFunc<T>(Func<Unit, T> f) { 
    return f(null); 
} 

Đối với các hướng dẫn .tail - đó là cái gọi là "đuôi gọi tối ưu hóa".
Trong cuộc gọi chức năng thông thường, địa chỉ trả về sẽ được đẩy lên ngăn xếp (ngăn xếp CPU) và sau đó chức năng được gọi. Khi chức năng được thực hiện, nó thực thi lệnh "return" (trả về), lệnh này sẽ bật địa chỉ trả về khỏi ngăn xếp và chuyển điều khiển ở đó.
Tuy nhiên, khi chức năng A cuộc gọi chức năng B, và sau đó ngay lập tức trả về giá trị trả về chức năng B 's, mà không làm bất cứ điều gì khác, CPU có thể bỏ qua đẩy địa chỉ trả lại thêm trên stack, và thực hiện một 'nhảy' để B thay vì ", hãy gọi". Bằng cách đó, khi B thực hiện lệnh "trả về", CPU sẽ bật địa chỉ trả về từ ngăn xếp và địa chỉ đó sẽ trỏ tới không A, nhưng với bất kỳ ai được gọi là A ngay từ đầu.
Một cách khác để nghĩ về nó là: chức năng A cuộc gọi chức năng B không trước trở về, nhưng thay vì trở, và do đó các đại biểu vinh dự trở về B. Vì vậy, có hiệu lực, kỹ thuật ma thuật này cho phép chúng tôi gọi mà không cần tiêu tốn một điểm trên ngăn xếp, điều đó có nghĩa là bạn có thể thực hiện nhiều cuộc gọi như vậy mà không có nguy cơ tràn ngăn xếp. Điều này rất quan trọng trong lập trình hàm, bởi vì nó cho phép thực hiện hiệu quả các thuật toán đệ quy.

Nó được gọi là "cuộc gọi đuôi", bởi vì gọi tới B xảy ra, do đó, để nói, ở đuôi của A.

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