2011-12-30 13 views
25

Vì mục đích giáo dục, tôi đang viết một tập hợp các phương thức gây ra các ngoại lệ thời gian chạy trong C# để hiểu tất cả các ngoại lệ là gì và nguyên nhân gây ra chúng. Ngay bây giờ, tôi đang làm việc với các chương trình gây ra một số AccessViolationException.Tại sao không * (int *) 0 = 0 gây ra vi phạm truy cập?

Cách rõ ràng nhất (với tôi) để làm điều này là để viết thư cho một vị trí bộ nhớ được bảo vệ, như thế này:

System.Runtime.InteropServices.Marshal.WriteInt32(IntPtr.Zero, 0); 

Cũng như tôi đã hy vọng, điều này đã ném một AccessViolationException. Tôi muốn làm điều đó một cách chính xác hơn, vì vậy tôi quyết định viết một chương trình với mã không an toàn, và làm (những gì tôi nghĩ là) chính xác điều tương tự bằng cách gán 0 cho con trỏ 0.

unsafe 
{ 
    *(int*)0 = 0; 
} 

Vì lý do khiến tôi khó chịu, điều này ném một số NullReferenceException. Tôi chơi xung quanh với nó một số và phát hiện ra rằng bằng cách sử dụng *(int*)1 thay vì cũng ném một NullReferenceException, nhưng nếu bạn sử dụng một số âm, như *(int*)-1 nó sẽ ném một AccessViolationException.

Điều gì đang xảy ra ở đây? Tại sao *(int*)0 = 0 gây ra một số NullReferenceException và tại sao nó không gây ra một số AccessViolationException?

+1

'(int *) 0' là một con trỏ rỗng. Tôi hoàn toàn mong đợi một 'NullReferenceException'. Nếu bạn muốn một 'AccessViolationException', hãy thử một cái gì đó như' (int *) 0x10' (hoặc có thể '0xf0000000'). – cHao

+7

có thể trùng lặp của [Tại sao truy cập bộ nhớ trong không gian địa chỉ thấp nhất (không mặc định) được báo cáo là NullReferenceException bởi .NET?] (Http://stackoverflow.com/questions/7940492/why-is-memory-access-in - thấp nhất-địa chỉ-không gian-không-null-mặc dù-báo cáo-as-n) –

Trả lời

28

Ngoại lệ tham chiếu null xảy ra khi bạn bỏ qua con trỏ rỗng; CLR không quan tâm liệu con trỏ null có phải là một con trỏ không an toàn với số nguyên không được gắn vào nó hay một con trỏ được quản lý (có nghĩa là một tham chiếu đến một đối tượng kiểu tham chiếu) mà không bị mắc kẹt vào nó.

CLR biết rằng null đã bị hủy bỏ như thế nào? Và CLR biết như thế nào khi một số con trỏ không hợp lệ khác bị hủy đăng ký? Mỗi con trỏ trỏ đến đâu đó trong một trang của bộ nhớ ảo trong không gian địa chỉ bộ nhớ ảo của tiến trình. Hệ điều hành theo dõi các trang nào hợp lệ và không hợp lệ; khi bạn chạm vào một trang không hợp lệ, nó sẽ đưa ra một ngoại lệ được CLR phát hiện. CLR sau đó bề mặt đó như là một ngoại lệ truy cập không hợp lệ hoặc một ngoại lệ tham chiếu null.

Nếu quyền truy cập không hợp lệ là 64K dưới cùng của bộ nhớ, đó là một ngoại lệ không có ngoại lệ. Nếu không, nó là một ngoại lệ truy cập không hợp lệ.

Điều này giải thích lý do tại sao dereferencing zero và một cung cấp cho một ngoại lệ ref rỗng, và lý do tại sao dereferencing -1 cho một ngoại lệ truy cập không hợp lệ; -1 là con trỏ 0xFFFFFFFF trên các máy 32 bit và trang cụ thể đó (trên các máy x86) luôn được dành riêng cho hệ điều hành để sử dụng cho mục đích riêng của nó. Mã người dùng không thể truy cập nó.

Bây giờ, bạn có thể hỏi lý do tại sao không chỉ làm ngoại lệ tham chiếu null cho con trỏ 0 và ngoại lệ truy cập không hợp lệ cho mọi thứ khác? Bởi vì phần lớn thời gian khi một số nhỏ bị hủy bỏ, đó là bởi vì bạn đã nhận được nó thông qua một tham chiếu null. Hãy tưởng tượng ví dụ mà bạn đã cố gắng để làm:

int* p = (int*)0; 
int x = p[1]; 

Trình biên dịch dịch đó vào tương đương với đạo đức của:

int* p = (int*)0; 
int x = *((int*)((int)p + 1 * sizeof(int))); 

được dereferencing 4. Nhưng từ quan điểm của người dùng, p[1] chắc chắn trông giống như một dereference của null! Vì vậy, đó là lỗi được báo cáo.

+0

Câu trả lời tuyệt vời (và rất thú vị)! +1 –

2

Các NullReferenceException bang mà "Trường hợp ngoại lệ được ném khi có một nỗ lực để dereference một tham chiếu đối tượng null", vì vậy kể từ *(int*)0 = 0 cố gắng để thiết lập vị trí bộ nhớ 0x000 sử dụng một đối tượng dereference nó sẽ ném một NullReferenceException. Lưu ý rằng ngoại lệ này được ném trước khi cố truy cập ngay cả bộ nhớ.

Các AccessViolationException lớp trên các tiểu bang Mặt khác đó, "Trường hợp ngoại lệ được ném khi có một nỗ lực để đọc hay ghi bộ nhớ được bảo vệ", và kể từ System.Runtime.InteropServices.Marshal.WriteInt32(IntPtr.Zero, 0) không sử dụng một dereference, thay vì cố gắng để thiết lập bộ nhớ sử dụng phương pháp này, một đối tượng không bị hủy bỏ, do đó nghĩa là không có NullReferenceException sẽ bị ném.

+1

Điều đó không hoàn toàn đúng. 'WriteInt32' _does_ dereference con trỏ, nhưng bắt NRE và ném một vi phạm truy cập để thay thế. – Daniel

+0

Câu trả lời này là sai. Câu trả lời đúng là ở đây: http://stackoverflow.com/a/7940659/162396 – Daniel

4

Đây không phải là câu trả lời, nhưng nếu bạn dịch ngược WriteInt32 bạn thấy nó bắt được NullReferenceException và ném một số AccessViolationException. Vì vậy, hành vi có thể giống nhau, nhưng bị che giấu bởi ngoại lệ thực sự bị bắt và một ngoại lệ khác được nâng lên.

1

MSDN nói rằng rõ ràng:

Trong chương trình bao gồm mã hoàn toàn có thể kiểm chứng của quản lý, tất cả các tài liệu tham khảo là một trong hai giá trị hoặc null, và vi phạm truy cập là không thể. AccessViolationException chỉ xảy ra khi mã được xác minh được quản lý tương tác với mã không được quản lý hoặc với mã không được quản lý an toàn .

Xem trợ giúp AccessViolationException.

+0

Điều đó không giải thích lý do tại sao hai đoạn này lại ném các ngoại lệ khác nhau. – Daniel

+0

Daniel, đó là trong thực tế giải thích nó. Tôi đã không xây dựng nó như đã có giải thích chi tiết từ JSPerfUnkn0wn. – Yakeen

+0

Daniel, lời giải thích từ Hans bạn đề cập đến âm thanh hợp lý. – Yakeen

0

Đây là cách CLR hoạt động. Thay vì kiểm tra nếu đối tượng địa chỉ == null cho mỗi trường truy cập, nó chỉ truy cập nó. Nếu nó đã được null - CLR bắt GPF và rethrow nó như NullReferenceException. Không có vấn đề gì loại tham chiếu nó được.