Các giá trị null được lưu trữ ở đâu, hoặc chúng được lưu trữ ở tất cả?


39

Tôi muốn tìm hiểu về giá trị null hoặc tài liệu tham khảo null.

Ví dụ, tôi có một lớp gọi là Apple và tôi đã tạo một thể hiện của nó.

Apple myApple = new Apple("yummy"); // The data is stored in memory

Sau đó, tôi đã ăn quả táo đó và bây giờ nó cần phải là null, vì vậy tôi đặt nó là null.

myApple = null;

Sau cuộc gọi này, tôi quên rằng tôi đã ăn nó và bây giờ muốn kiểm tra.

bool isEaten = (myApple == null);

Với cuộc gọi này, myApple đang tham khảo ở đâu? Null là một giá trị con trỏ đặc biệt? Nếu vậy, nếu tôi có 1000 đối tượng null, chúng có chiếm 1000 không gian bộ nhớ đối tượng hay 1000 không gian bộ nhớ int nếu chúng ta nghĩ một loại con trỏ là int?

Câu trả lời:


45

Trong ví dụ của bạn myApplecó giá trị đặc biệt null(thường là tất cả các bit 0) và vì vậy không tham chiếu gì cả. Các đối tượng mà nó ban đầu được đề cập bây giờ bị mất trên heap. Không có cách nào để lấy lại vị trí của nó. Điều này được gọi là rò rỉ bộ nhớ trên các hệ thống không có bộ sưu tập rác.

Nếu ban đầu bạn đặt 1000 tham chiếu thành null, thì bạn có không gian cho 1000 tham chiếu, thường là 1000 * 4 byte (trên hệ thống 32 bit, gấp đôi so với 64). Nếu 1000 tham chiếu đó ban đầu chỉ vào các đối tượng thực, thì bạn đã phân bổ 1000 lần kích thước của mỗi đối tượng, cộng với không gian cho 1000 tham chiếu.

Trong một số ngôn ngữ (như C và C ++), con trỏ luôn trỏ đến một cái gì đó, ngay cả khi "chưa được khởi tạo". Vấn đề là địa chỉ họ giữ có hợp pháp để chương trình của bạn truy cập hay không. Địa chỉ đặc biệt zero (aka null) cố tình không được ánh xạ vào không gian địa chỉ của bạn, do đó, lỗi phân đoạn được tạo bởi đơn vị quản lý bộ nhớ (MMU) khi nó được truy cập và chương trình của bạn gặp sự cố. Nhưng kể từ địa chỉ không được cố tình không ánh xạ trong, nó sẽ trở thành một giá trị lý tưởng để sử dụng để chỉ ra rằng một con trỏ không trỏ đến bất cứ điều gì, vì thế vai trò của nó như là null. Để hoàn thành câu chuyện, khi bạn phân bổ bộ nhớ với newhoặcmalloc(), hệ điều hành cấu hình MMU để ánh xạ các trang RAM vào không gian địa chỉ của bạn và chúng có thể sử dụng được. Thông thường vẫn còn rất nhiều không gian địa chỉ không được ánh xạ và do đó cũng dẫn đến các lỗi phân đoạn.


Giải thích rất tốt.
NoChance

6
Đó là một chút sai về phần "rò rỉ bộ nhớ". Đó là rò rỉ bộ nhớ trong các hệ thống mà không có quản lý bộ nhớ tự động. Tuy nhiên, GC không phải là cách khả thi duy nhất để thực hiện quản lý bộ nhớ tự động. C ++ std::shared_ptr<Apple>là một ví dụ không phải là GC cũng không rò rỉ Applekhi zeroed.
MSalters

1
@MSalters - Không phải shared_ptrchỉ là một hình thức cơ bản để thu gom rác sao? GC không yêu cầu phải có một "trình thu gom rác" riêng biệt, chỉ có việc thu gom rác xảy ra.
Phục hồi Monica

5
@Brendan: Thuật ngữ "bộ sưu tập rác" hầu như được hiểu một cách phổ biến để chỉ bộ sưu tập không xác định diễn ra độc lập với đường dẫn mã thông thường. Phá hủy xác định dựa trên đếm tham chiếu là một cái gì đó hoàn toàn khác nhau.
Mason Wheeler

1
Lời giải thích hay. Một điểm hơi sai lệch là giả định rằng phân bổ bộ nhớ ánh xạ tới RAM. RAM là một cơ chế lưu trữ bộ nhớ ngắn hạn, nhưng cơ chế lưu trữ thực tế được HĐH trừu tượng hóa. Trong Windows (đối với các ứng dụng không đổ chuông), các trang bộ nhớ được ảo hóa và có thể ánh xạ tới RAM, tệp trao đổi đĩa hoặc có lẽ là một thiết bị lưu trữ khác.
Simon Gillbee

13

Câu trả lời phụ thuộc vào ngôn ngữ bạn đang sử dụng.

C / C ++

Trong C và C ++, từ khóa là NULL và NULL thực sự là 0. Người ta đã quyết định rằng "0x0000" sẽ không bao giờ trở thành một con trỏ hợp lệ cho một đối tượng và vì vậy đó là giá trị được gán để chỉ ra rằng nó không phải là một con trỏ hợp lệ. Tuy nhiên, nó hoàn toàn tùy ý. Nếu bạn đã cố truy cập nó như một con trỏ, nó sẽ hoạt động chính xác như một con trỏ tới một đối tượng không còn tồn tại trong bộ nhớ, khiến cho một ngoại lệ con trỏ không hợp lệ bị ném. Con trỏ tự chiếm bộ nhớ, nhưng không nhiều hơn một đối tượng số nguyên. Do đó, nếu bạn có 1000 con trỏ null, nó tương đương với 1000 số nguyên. Nếu một số trong các con trỏ đó trỏ đến các đối tượng hợp lệ, thì việc sử dụng bộ nhớ sẽ tương đương với 1000 số nguyên cộng với bộ nhớ có trong các con trỏ hợp lệ đó. Hãy nhớ rằng trong C hoặc C ++,không ngụ ý bộ nhớ đã được phát hành, vì vậy bạn phải xóa rõ ràng đối tượng đó bằng cách sử dụng dealloc (C) hoặc xóa (C ++).

Java

Không giống như trong C và C ++, trong Java null chỉ là một từ khóa. Thay vì quản lý null như một con trỏ tới một đối tượng, nó được quản lý bên trong và được xử lý như một nghĩa đen. Điều này đã loại bỏ sự cần thiết phải liên kết các con trỏ dưới dạng các số nguyên và cho phép Java hoàn toàn loại bỏ các con trỏ. Tuy nhiên, ngay cả khi Java che giấu nó tốt hơn, chúng vẫn là các con trỏ, có nghĩa là 1000 con trỏ null vẫn tiêu thụ tương đương với 1000 số nguyên. Rõ ràng khi chúng trỏ đến các đối tượng, giống như C và C ++, bộ nhớ bị tiêu thụ bởi các đối tượng đó cho đến khi không còn con trỏ tham chiếu đến chúng, tuy nhiên không giống như trong C và C ++, trình thu gom rác nhặt nó trên đường tiếp theo của nó và giải phóng bộ nhớ, mà không yêu cầu bạn phải theo dõi những đối tượng nào được giải phóng và đối tượng nào không, trong hầu hết các trường hợp (trừ khi bạn có lý do để tham chiếu các đối tượng yếu chẳng hạn).


9
Sự khác biệt của bạn không đúng: trên thực tế, trong C và C ++, con trỏ null hoàn toàn không cần trỏ đến địa chỉ bộ nhớ 0 (mặc dù đây là cách triển khai tự nhiên, giống như trong Java và C #). Nó có thể chỉ ra bất cứ nơi nào theo nghĩa đen. Điều này hơi bị bối rối bởi thực tế là chữ-0 có thể được chuyển đổi hoàn toàn thành một con trỏ null. Nhưng mẫu bit được lưu trữ cho một con trỏ null vẫn không cần phải là tất cả các số không.
Konrad Rudolph

1
Không, bạn sai rồi. Các ngữ nghĩa hoàn toàn minh bạch trong chương trình, các con trỏ null và macro NULL( không phải là một từ khóa, nhân tiện) được xử lý như thể chúng là các bit không. Nhưng họ không cần phải được thực hiện như vậy, và trong thực tế một số hiện thực mơ hồ làm sử dụng khác không rỗng con trỏ. Nếu tôi viết if (myptr == 0)thì trình biên dịch sẽ thực hiện đúng, ngay cả khi con trỏ null được biểu diễn bên trong bởi 0xabcdef.
Konrad Rudolph

3
@Neil: hằng số con trỏ null (giá trị của kiểu số nguyên ước tính bằng 0) có thể chuyển đổi thành giá trị con trỏ null . (§4.10 C ++ 11.) Giá trị con trỏ null không được đảm bảo có tất cả các bit bằng không. 0là hằng số con trỏ rỗng, nhưng điều này không có nghĩa là myptr == 0kiểm tra xem tất cả các bit myptrcó bằng không.
Mat

5
@Neil: Bạn có thể muốn kiểm tra mục này trong C faq hoặc câu hỏi SO này
hugomg

1
@Neil Đó là lý do tại sao tôi không hề đề cập đến NULLmacro, thay vào đó, nói về con trỏ null null, và đề cập rõ ràng rằng có thể chuyển đổi hoàn toàn thành một con trỏ null.
Konrad Rudolph

5

Một con trỏ chỉ đơn giản là một biến chủ yếu thuộc kiểu số nguyên. Nó chỉ định một địa chỉ bộ nhớ nơi lưu trữ đối tượng thực tế.

Hầu hết các ngôn ngữ cho phép truy cập các thành viên đối tượng thông qua biến con trỏ này:

int localInt = myApple.appleInt;

Trình biên dịch biết cách truy cập các thành viên của một Apple. Nó "theo" con trỏ đến myAppleđịa chỉ của và lấy giá trị củaappleInt

Nếu bạn gán con trỏ null cho một biến con trỏ, bạn tạo con trỏ trỏ tới không có địa chỉ bộ nhớ. (Điều này làm cho không thể truy cập thành viên.)

Đối với mỗi con trỏ, bạn cần bộ nhớ để giữ giá trị số nguyên địa chỉ bộ nhớ (chủ yếu là 4 byte trên hệ thống 32 bit, 8 byte trên hệ thống 64 bit). Điều này cũng đúng với con trỏ null.


Tôi nghĩ rằng các biến / đối tượng tham chiếu không chính xác con trỏ. Nếu bạn in chúng, chúng chứa ClassName @ Hashcode. JVM sử dụng nội bộ Hashtable để lưu trữ Hashcode với địa chỉ thực tế và sử dụng Thuật toán Hash để lấy địa chỉ thực tế khi cần thiết.
trừ

@minusSeven Điều đó đúng với những gì liên quan đến các đối tượng theo nghĩa đen như số nguyên. Mặt khác, hàm băm giữ con trỏ tới các đối tượng khác có trong chính lớp Apple.
Neil

@minusSeven: Tôi đồng ý. Các chi tiết thực hiện con trỏ phụ thuộc rất nhiều vào ngôn ngữ / thời gian chạy. Nhưng tôi nghĩ những chi tiết đó không liên quan đến câu hỏi cụ thể.
Stephan

4

Ví dụ nhanh (lưu ý tên biến không được lưu trữ):

void main()
{
  int X = 3;
  int *Y = X;
  int *Z = null;
} // void main(...)


...........................
....+-----+--------+.......
....|     |   X    |.......
....+-----+--------+.......
....| 100 |   3    |<---+..
....+-----+--------+....|..
........................|..
....+-----+--------+....|..
....|     |   Y    |....|..
....+-----+--------+....|..
....| 102 |  100   +----+..
....+-----+--------+.......
...........................
....+-----+--------+.......
....|     |   z    |.......
....+-----+--------+.......
....| 104 |   0    |.......
....+-----+--------+.......
...........................

Chúc mừng.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.