provably không thể:
Vào thời điểm b
được gọi, không gian trong ngăn xếp được sử dụng bởi một là arg1
(stack IL, vì vậy có thể nó không bao giờ thậm chí đặt trong một ngăn xếp, nhưng đã bị enregistered trên cuộc gọi) không được đảm bảo vẫn được sử dụng bởi arg1
.
Nếu gia hạn, nếu arg1
là loại tham chiếu, đối tượng được đề cập không được đảm bảo là không thu gom rác, nếu không được sử dụng sau khi gọi tới b
.
Sửa:
Một chút chi tiết hơn, kể từ khi bình luận của bạn cho thấy bạn không grokking này và vẫn nghĩ rằng nó nên có thể.
Quy ước gọi được sử dụng bởi jitter không được chỉ định trong thông số kỹ thuật cho bất kỳ tiêu chuẩn liên quan nào, cho phép người thực hiện tự do cải tiến. Chúng thực sự khác nhau giữa các phiên bản 32 bit và 64 bit và các phiên bản khác nhau.
Tuy nhiên, các bài viết từ MS cho rằng quy ước được sử dụng tương tự như quy ước __fastcall. Trong cuộc gọi của bạn đến a
, arg1
sẽ được đưa vào thanh ghi ECX * và arg2
vào thanh ghi EDX (tôi đơn giản hóa bằng cách giả sử 32 bit x86, với amd64 nhiều đối số hơn được đăng ký) của lõi mã đang chạy . arg3
sẽ được đẩy vào ngăn xếp và thực sự sẽ tồn tại trong bộ nhớ. Lưu ý rằng tại thời điểm này, không có vị trí bộ nhớ trong đó arg1
và arg2
tồn tại, chúng chỉ nằm trong thanh ghi CPU.
Trong quá trình thực thi chính phương thức, thanh ghi và bộ nhớ được sử dụng khi cần thiết. Và b
được gọi.
Bây giờ, nếu a
cần arg1
hoặc arg2
, nó sẽ phải đẩy trước khi gọi b
. Nhưng nếu không, thì nó sẽ không - và mọi thứ thậm chí có thể được sắp xếp lại để giảm nhu cầu này. Ngược lại, những thanh ghi đó có thể đã được sử dụng cho cái gì đó đã có trước thời điểm này - jitter không ngu ngốc, vì vậy nếu nó cần một thanh ghi hoặc một khe trên ngăn xếp và có một cái không được sử dụng cho phần còn lại của phương thức, nó sẽ để tái sử dụng không gian đó. (Đối với vấn đề đó, ở cấp độ trên, trình biên dịch C# sẽ sử dụng lại các khe trong ngăn xếp ảo mà IL đã sử dụng).
Vì vậy, khi b
được gọi, arg4
được đặt trong ECX đăng ký, arg5
vào EDX và arg6
được đẩy lên ngăn xếp. Tại thời điểm này, arg1
và arg2
không tồn tại và bạn không thể tìm ra chúng là gì hơn là bạn có thể đọc một cuốn sách sau khi nó đã được tái chế và biến thành giấy vệ sinh.
(Lưu ý thú vị là nó rất phổ biến cho một phương thức gọi một phương thức khác với cùng một đối số ở cùng một vị trí, trong trường hợp này ECX và EDX có thể chỉ còn lại một mình).
Sau đó, b
trả về, đặt giá trị trả về trong sổ đăng ký EAX, hoặc cặp EDX: EAX hoặc bộ nhớ có EAX trỏ đến nó, tùy thuộc vào kích thước, a
thực hiện thêm một số thao tác trước khi trả lại trong thanh ghi đó và Sớm.
Bây giờ, điều này giả định không có bất kỳ tối ưu hóa nào được thực hiện. Có thể thực tế, b
hoàn toàn không được gọi, mà đúng hơn là mã của nó được gạch chân. Trong trường hợp này cho dù giá trị trong sổ đăng ký hoặc trên ngăn xếp - và trong trường hợp thứ hai, nơi chúng ở trên ngăn xếp, không còn liên quan gì đến chữ ký của b
và mọi thứ cần làm với giá trị có liên quan trong thời gian a
thực hiện và sẽ khác trong trường hợp có một "cuộc gọi" khác tới b
hoặc thậm chí trong trường hợp có một "cuộc gọi" khác tới số b
từ a
, vì toàn bộ cuộc gọi của a
bao gồm cả cuộc gọi đến b
có thể đã được gạch chân trong một trường hợp, không được gạch chân trong một trường hợp khác và được gạch chân khác nhau. Ví dụ: arg4
xuất phát trực tiếp từ một giá trị được trả về bởi một cuộc gọi khác, có thể là trong sổ đăng ký EAX tại thời điểm này, trong khi arg5
ở ECX vì nó giống như arg1
và arg6
ở một nơi nào đó ở giữa ngăn xếp không gian được sử dụng bởi a
. Một khả năng khác là cuộc gọi đến b
là một cuộc gọi đuôi đã bị loại bỏ: Bởi vì cuộc gọi đến b
sẽ có giá trị trả lại ngay lập tức được trả về a
(hoặc một số khả năng khác), sau đó thay vì đẩy ngăn xếp, các giá trị được sử dụng bởi a
được thay thế tại chỗ và địa chỉ trả lại thay đổi để trả về từ b
nhảy trở lại phương thức được gọi là a
, bỏ qua một số công việc (và giảm mức sử dụng bộ nhớ ở mức độ phương pháp tiếp cận phong cách chức năng mà sẽ tràn ngăn xếp thay vì làm việc và thực sự làm việc tốt). Trong trường hợp này, trong khi gọi tới b
, các tham số đến a
có thể hoàn toàn biến mất, ngay cả những thông số đã có trong ngăn xếp.
Rất đáng tranh cãi cho dù trường hợp cuối cùng này thậm chí có được coi là tối ưu hóa hay không; một số ngôn ngữ phụ thuộc nhiều vào nó được thực hiện như với nó họ cung cấp cho hiệu suất tốt và không có họ cho hiệu suất khủng khiếp nếu họ thậm chí làm việc ở tất cả (thay vì tràn ngăn xếp).
Có thể có tất cả các cách tối ưu hóa khác. Có nên là tất cả các cách tối ưu hóa khác - nếu nhóm .NET hoặc nhóm Mono làm điều gì đó làm cho mã của tôi nhanh hơn hoặc sử dụng ít bộ nhớ hơn, nhưng nếu không có thứ gì đó, tôi sẽ không phàn nàn!
Và đó giả định rằng người viết C# ở nơi đầu tiên không bao giờ thay đổi giá trị của một tham số, mà chắc chắn sẽ không đúng.Hãy xem xét mã này:
IEnumerable<T> RepeatedlyInvoke(Func<T> factory, int count)
{
if(count < 0)
throw new ArgumentOutOfRangeException();
while(count-- != 0)
yield return factory();
}
Thậm chí nếu trình biên dịch C# và jitter đã được thiết kế theo một cách lãng phí như vậy mà bạn có thể đảm bảo các thông số không được thay đổi trong cách mô tả ở trên, làm thế nào bạn có thể biết những gì count
đã đã từ trong lời gọi factory
? Ngay cả trong lần gọi đầu tiên, nó cũng khác, và nó không giống như ở trên là mã lạ.
Vì vậy, trong bản tóm tắt:
- Jitter: Các thông số thường enregistered. Bạn có thể mong đợi x86 đặt 2 tham số con trỏ, tham chiếu hoặc số nguyên trong thanh ghi và amd64 để đặt 4 tham số con trỏ, tham chiếu hoặc số nguyên và 4 tham số dấu chấm động vào thanh ghi. Họ không có vị trí để đọc chúng.
- Jitter: Thông số trên ngăn xếp thường được ghi đè.
- Jitter: Có thể không có một cuộc gọi thực sự nào cả, vì vậy không có nơi nào để tìm kiếm các thông số vì chúng có thể ở bất cứ đâu.
- Jitter: "cuộc gọi" có thể được tái sử dụng cùng một khung như hình cuối cùng.
- Trình biên dịch: IL có thể sử dụng lại các khe cho người dân địa phương.
- Con người: Người lập trình có thể thay đổi giá trị tham số.
Từ tất cả những điều đó, làm thế nào để có thể biết được arg1
là gì?
Bây giờ, hãy thêm vào sự tồn tại của bộ sưu tập rác. Hãy tưởng tượng nếu chúng ta có thể kỳ diệu biết những gì arg1
là anyway, mặc dù tất cả điều này. Nếu nó là một tham chiếu đến một đối tượng trên heap, nó vẫn có thể làm chúng ta không tốt, bởi vì nếu tất cả những điều trên có nghĩa là không có thêm tài liệu tham khảo nào đang hoạt động trên stack - và rõ ràng là điều này chắc chắn không xảy ra - và GC đá vào, sau đó đối tượng có thể đã được thu thập. Vì vậy, tất cả những gì chúng ta kỳ diệu có thể giữ là một tham chiếu đến cái gì đó không còn tồn tại - thực sự có thể là một khu vực trong heap hiện đang được sử dụng cho cái gì khác, bang đi toàn bộ loại an toàn của toàn bộ khuôn khổ!
Nó không phải trong các bit nhỏ nhất tương đương với phản ánh việc thu thập các IL, bởi vì:
- Các IL là tĩnh, chứ không phải chỉ là một nhà nước tại một thời điểm cho trước. Tương tự như vậy, chúng tôi có thể nhận được một bản sao các cuốn sách yêu thích của chúng tôi từ một thư viện dễ dàng hơn nhiều so với chúng tôi có thể lấy lại phản ứng của chúng tôi trong lần đầu tiên chúng tôi đọc chúng.
- IL không phản ánh tác động của nội tuyến, v.v. Nếu một cuộc gọi được inlined mỗi khi nó thực sự được sử dụng, và sau đó chúng tôi sử dụng sự phản chiếu để có được một phương pháp đó, một thực tế là nó thường được gạch chân là không liên quan.
Các đề xuất trong các câu trả lời khác về lược tả, AOP và chặn cũng gần như bạn sắp nhận được.
* Thực ra, this
là thông số đầu tiên thực sự cho các thành viên thể hiện. Cho phép giả vờ tất cả mọi thứ là tĩnh vì vậy chúng tôi không phải tiếp tục chỉ ra điều này.
Câu hỏi thú vị ... không thể có trong C#, nhưng có thể trong IL hoặc sử dụng sự phản chiếu? –
Đặt cược của tôi là điều đó là không thể. Nó sẽ làm cho một câu hỏi phỏng vấn độc đáo tốt, mặc dù (tưởng tượng rằng nó là có thể - làm thế nào để bạn nghĩ rằng nó sẽ làm việc?), Cách tốt hơn so với hầu hết các "câu hỏi kỳ quặc" điển hình; một điểm khởi đầu tốt để tiết lộ kiến thức tổng thể của C# và CLR, vv –
Về mặt lý thuyết, NÊN có thể. Stacktrace bình thường trả về thông tin về các hàm đang được gọi, cũng như các kiểu đối số của chúng và tất cả các biến được sử dụng để gọi hàm sẽ được lưu trữ trong ngăn xếp ở đâu đó (mặc dù có thể là biến cục bộ). –