เก็บค่า Null ไว้ที่ไหนหรือเก็บไว้ที่ไหน?


39

ฉันต้องการเรียนรู้เกี่ยวกับค่า Null หรือการอ้างอิง Null

ตัวอย่างเช่นฉันมีคลาสที่เรียกว่า Apple และฉันสร้างอินสแตนซ์ของมัน

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

จากนั้นฉันกินแอปเปิ้ลนั้นและตอนนี้มันต้องเป็นโมฆะดังนั้นฉันตั้งมันเป็นโมฆะ

myApple = null;

หลังจากสายนี้ฉันลืมว่าฉันกินมันและตอนนี้ต้องการตรวจสอบ

bool isEaten = (myApple == null);

ด้วยการโทรนี้การอ้างอิง myApple อยู่ที่ไหน เป็นค่าตัวชี้พิเศษหรือไม่ ถ้าเป็นเช่นนั้นถ้าฉันมีวัตถุ 1,000 NULL พวกเขาครอบครองพื้นที่หน่วยความจำวัตถุ 1,000 หรือ 1,000 พื้นที่หน่วยความจำ int ถ้าเราคิดว่าประเภทตัวชี้เป็น int?

คำตอบ:


45

ในตัวอย่างของคุณmyAppleมีค่าพิเศษnull(โดยทั่วไปจะเป็นศูนย์ทั้งหมด) และไม่มีการอ้างอิงอะไรเลย วัตถุที่อ้างถึงตอนแรกหายไปในกอง ไม่มีวิธีในการเรียกคืนตำแหน่ง สิ่งนี้เรียกว่าการรั่วไหลของหน่วยความจำบนระบบที่ไม่มีการรวบรวมขยะ

หากคุณตั้งค่าการอ้างอิง 1,000 ครั้งเป็น null คุณจะมีที่ว่างสำหรับการอ้างอิงเพียง 1,000 รายการโดยทั่วไปคือ 1,000 * 4 ไบต์ (บนระบบ 32 บิตสองครั้งที่ 64) หากการอ้างอิง 1,000 รายการนั้นชี้ไปที่วัตถุจริงคุณจะจัดสรร 1,000 เท่าของขนาดของวัตถุแต่ละรายการรวมทั้งมีที่ว่างสำหรับการอ้างอิง 1,000 รายการ

ในบางภาษา (เช่น C และ C ++) พอยน์เตอร์จะชี้ไปที่บางสิ่งเสมอแม้จะเป็น "ไม่กำหนดค่าเริ่มต้น" ก็ตาม ปัญหาคือว่าที่อยู่ที่พวกเขาถือนั้นถูกกฎหมายสำหรับโปรแกรมของคุณในการเข้าถึง ศูนย์ที่อยู่พิเศษ (aka null) จะไม่ถูกแมปเข้ากับพื้นที่ที่อยู่ของคุณโดยเจตนาดังนั้นความผิดพลาดในการแบ่งส่วนจะถูกสร้างขึ้นโดยหน่วยจัดการหน่วยความจำ (MMU) เมื่อมีการเข้าถึงและโปรแกรมของคุณขัดข้อง แต่เนื่องจากที่อยู่ศูนย์จะจงใจไม่nullแมปในมันจะกลายเป็นค่าที่เหมาะที่จะใช้ในการระบุว่าเป็นตัวชี้ไม่ได้ชี้ไปที่อะไรเพราะฉะนั้นบทบาทของการเป็น เพื่อให้เรื่องราวสำเร็จตามที่คุณจัดสรรหน่วยความจำด้วยnewหรือmalloc()ระบบปฏิบัติการกำหนดค่า MMU เพื่อจับคู่หน้า RAM กับพื้นที่ที่อยู่ของคุณและสามารถใช้งานได้ โดยทั่วไปแล้วยังมีช่วงกว้างของพื้นที่ที่อยู่ที่ไม่ได้ถูกแมปและนำไปสู่ข้อผิดพลาดในการแบ่งกลุ่มเช่นกัน


คำอธิบายที่ดีมาก
NoChance

6
มันผิดเล็กน้อยในส่วน "หน่วยความจำรั่ว" มันเป็นความจำรั่วในระบบที่ไม่มีการจัดการหน่วยความจำอัตโนมัติ อย่างไรก็ตาม GC ไม่ใช่วิธีเดียวที่เป็นไปได้ในการใช้การจัดการหน่วยความจำอัตโนมัติ C ++ std::shared_ptr<Apple>เป็นตัวอย่างที่ไม่ใช่ GC และไม่มีการรั่วไหลAppleเมื่อเป็นศูนย์
MSalters

1
@MSalters - ไม่ใช่shared_ptrรูปแบบพื้นฐานสำหรับการรวบรวมขยะหรือไม่ GC ไม่ต้องการให้มี "ตัวรวบรวมขยะ" แยกต่างหาก แต่จะมีการรวบรวมขยะเท่านั้น
Reinstate Monica

5
@Brendan: คำว่า "การรวบรวมขยะ" เป็นที่เข้าใจกันทั่วไปในระดับสากลเพื่ออ้างถึงการรวบรวมที่ไม่ได้กำหนดขึ้นโดยอิสระจากเส้นทางของรหัสปกติ การทำลายที่กำหนดขึ้นอยู่กับการนับการอ้างอิงเป็นสิ่งที่แตกต่างอย่างสิ้นเชิง
Mason Wheeler

1
คำอธิบายที่ดี หนึ่งจุดที่ทำให้เข้าใจผิดเล็กน้อยคือการสันนิษฐานว่าการจัดสรรหน่วยความจำแมปกับ RAM RAM เป็นกลไกหนึ่งสำหรับการจัดเก็บหน่วยความจำระยะสั้น แต่กลไกการจัดเก็บที่แท้จริงนั้นเป็นนามธรรมโดย OS ใน Windows (สำหรับแอพที่ไม่ได้เป็นศูนย์) หน้าหน่วยความจำจะถูกจำลองเสมือนจริงและอาจแมปกับ RAM, ไฟล์สลับดิสก์หรืออุปกรณ์เก็บข้อมูลอื่น
Simon Gillbee

13

คำตอบขึ้นอยู่กับภาษาที่คุณใช้

C / C ++

ใน C และ C ++ คำสำคัญคือ NULL และ NULL ที่แท้จริงคืออะไรคือ 0 มีการตัดสินใจว่า "0x0000" ไม่เคยจะเป็นตัวชี้ที่ถูกต้องสำหรับวัตถุและนั่นคือค่าที่ได้รับมอบหมายเพื่อระบุว่ามันเป็น ไม่ใช่ตัวชี้ที่ถูกต้อง อย่างไรก็ตามมันเป็นเรื่องโดยพลการ หากคุณพยายามเข้าถึงมันเหมือนตัวชี้มันจะทำงานเหมือนกับตัวชี้ไปยังวัตถุที่ไม่มีอยู่ในหน่วยความจำอีกต่อไปทำให้เกิดข้อยกเว้นตัวชี้ที่ไม่ถูกต้อง ตัวชี้นั้นใช้หน่วยความจำ แต่จะมีวัตถุจำนวนเต็มไม่เกิน ดังนั้นถ้าคุณมีพอยน์เตอร์ 1,000 ตัวมันก็เท่ากับ 1,000 จำนวนเต็ม หากพอยน์เตอร์บางตัวชี้ไปที่วัตถุที่ถูกต้องการใช้งานหน่วยความจำจะเท่ากับ 1,000 จำนวนเต็มบวกหน่วยความจำที่มีอยู่ในพอยน์เตอร์ที่ถูกต้อง จำไว้ว่าใน C หรือ C ++ไม่ได้หมายความว่าหน่วยความจำได้รับการเผยแพร่ดังนั้นคุณต้องลบวัตถุนั้นอย่างชัดเจนโดยใช้ dealloc (C) หรือลบ (C ++)

ชวา

แตกต่างจากใน C และ C ++ ใน Java null เป็นเพียงคำหลัก แทนที่จะจัดการกับค่า null เช่นตัวชี้ไปยังวัตถุจะถูกจัดการภายในและถือว่าเป็นตัวอักษร สิ่งนี้ทำให้ไม่จำเป็นต้องผูกพอยน์เตอร์เป็นประเภทจำนวนเต็มและอนุญาตให้ Java ย่อพอยน์เตอร์ทั้งหมด อย่างไรก็ตามแม้ว่า Java จะซ่อนได้ดีกว่าพวกเขายังคงเป็นพอยน์เตอร์ซึ่งหมายความว่าพอยน์เตอร์ 1,000 null ยังคงใช้งานได้เทียบเท่า 1,000 จำนวนเต็ม เห็นได้ชัดว่าเมื่อพวกเขาชี้ไปที่วัตถุเช่น C และ C ++ หน่วยความจำจะถูกใช้โดยวัตถุเหล่านั้นจนกว่าจะไม่มีตัวชี้ที่อ้างอิงถึงพวกเขา แต่ต่างจากใน C และ C ++ ตัวเก็บรวบรวมขยะจะเก็บมันไว้ในรอบถัดไป ในกรณีส่วนใหญ่ (เว้นแต่ว่าคุณมีเหตุผลในการอ้างอิงวัตถุอย่างอ่อนเช่น)


9
ความแตกต่างของคุณไม่ถูกต้อง: อันที่จริงแล้วใน C และ C ++ ตัวชี้โมฆะไม่จำเป็นต้องชี้ไปยังที่อยู่หน่วยความจำ 0 เลย (แม้ว่าจะเป็นการใช้งานตามธรรมชาติเช่นเดียวกับใน Java และ C #) มันสามารถชี้ไปที่ใดก็ได้อย่างแท้จริง นี่คือความสับสนเล็กน้อยจากข้อเท็จจริงที่ว่าตัวอักษร -0 สามารถแปลงเป็นตัวชี้ null ได้โดยปริยาย แต่รูปแบบบิตที่เก็บไว้สำหรับตัวชี้ null ยังคงไม่จำเป็นต้องเป็นศูนย์ทั้งหมด
Konrad Rudolph

1
ไม่คุณผิด ซีแมนทิกส์นั้นโปร่งใส ... ในโปรแกรมพอยน์เตอร์พอยน์เตอร์และมาโครNULL( ไม่ใช่คำหลักโดยวิธีการ) จะได้รับการปฏิบัติเสมือนว่ามันเป็นศูนย์บิต แต่พวกเขาไม่จำเป็นต้องดำเนินการเช่นนี้และในความเป็นจริงการใช้งานที่ไม่ชัดเจนจะใช้พอยน์เตอร์ที่ไม่เป็นศูนย์ ถ้าผมเขียนแล้วคอมไพเลอร์จะทำสิ่งที่ถูกต้องแม้ว่าชี้โมฆะจะแสดงภายในโดยif (myptr == 0) 0xabcdef
Konrad Rudolph

3
@Neil กคงชี้โมฆะ (prvalue ของชนิดจำนวนเต็มที่ประเมินเป็นศูนย์) เป็นแปลงสภาพไปเป็นค่าชี้โมฆะ (§4.10 C ++ 11.) ค่าตัวชี้โมฆะไม่รับประกันว่าจะมีบิตทั้งหมดเป็นศูนย์ 0เป็นค่าคงที่ตัวชี้ null แต่นี่ไม่ได้หมายความว่าmyptr == 0ตรวจสอบว่าบิตทั้งหมดmyptrเป็นศูนย์หรือไม่
จ้า

5
@Neil: คุณอาจต้องการตรวจสอบรายการนี้ในคำถามที่พบบ่อย C หรือนี้คำถาม SO
hugomg

1
@ Neil นั่นคือเหตุผลที่ฉันไม่ต้องพูดถึงNULLแมโครเลยพูดถึง“ null pointer” และพูดอย่างชัดเจนว่า“ literal-0 สามารถแปลงเป็นตัวชี้ null ได้”
Konrad Rudolph

5

ตัวชี้เป็นเพียงตัวแปรซึ่งส่วนใหญ่เป็นประเภทจำนวนเต็ม มันระบุที่อยู่หน่วยความจำที่เก็บวัตถุจริง

ภาษาส่วนใหญ่อนุญาตให้เข้าถึงสมาชิกวัตถุผ่านตัวแปรตัวชี้นี้:

int localInt = myApple.appleInt;

Appleคอมไพเลอร์รู้วิธีที่จะเข้าถึงสมาชิกของนั้น มัน "ตาม" ตัวชี้ไปยังmyAppleที่อยู่ของและดึงค่าของappleInt

หากคุณกำหนดพอยน์เตอร์พอยน์เตอร์ให้กับตัวแปรพอยน์เตอร์คุณกำหนดให้พอยน์เตอร์ชี้ไม่มีที่อยู่หน่วยความจำ (ซึ่งทำให้การเข้าถึงสมาชิกเป็นไปไม่ได้)

สำหรับตัวชี้ทุกตัวคุณต้องมีหน่วยความจำเพื่อเก็บค่าจำนวนเต็มที่อยู่หน่วยความจำ (ส่วนใหญ่ 4 ไบต์บนระบบ 32 บิต, 8 ไบต์บนระบบ 64 บิต) สิ่งนี้เป็นจริงสำหรับพอยน์เตอร์พอยน์เตอร์ด้วยเช่นกัน


ฉันคิดว่าตัวแปร / วัตถุอ้างอิงไม่ใช่ตัวชี้อย่างแน่นอน หากคุณพิมพ์พวกเขาจะมี ClassName @ Hashcode JVM ใช้ Hashtable ภายในเพื่อจัดเก็บ Hashcode ด้วยที่อยู่จริงและใช้ Hash Algorithm เพื่อดึงข้อมูลที่อยู่จริงเมื่อจำเป็น
ลบ

@minusSeven นั้นถูกต้องสำหรับสิ่งที่เกี่ยวข้องกับวัตถุที่เป็นตัวอักษรเช่นจำนวนเต็ม มิฉะนั้น hashtable จะเก็บพอยน์เตอร์ไปยังวัตถุอื่น ๆ ที่อยู่ในคลาส Apple
Neil

@minusSeven: ฉันเห็นด้วย รายละเอียดของการใช้งานตัวชี้ขึ้นอยู่กับภาษา / รันไทม์อย่างมาก แต่ฉันคิดว่ารายละเอียดเหล่านั้นไม่เกี่ยวข้องกับคำถามเฉพาะ
เตฟาน

4

ตัวอย่างด่วน (ไม่ได้จัดเก็บชื่อหมายเหตุไว้):

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


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

ไชโย

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.