การลบตัวชี้ใน C ++


92

บริบท: ฉันพยายามห่อหัวตัวชี้เราเพิ่งเห็นพวกเขาเมื่อสองสามสัปดาห์ก่อนในโรงเรียนและขณะฝึกซ้อมวันนี้ฉันเจอเรื่องงี่เง่า? ปัญหาอาจตรงไปตรงมาสำหรับคุณ แต่ฉันไม่ค่อยมีประสบการณ์ในการเขียนโปรแกรมเลย

ฉันเคยเห็นคำถามไม่กี่ข้อใน SO เกี่ยวกับการลบพอยน์เตอร์ แต่ดูเหมือนว่าทั้งหมดจะเกี่ยวข้องกับการลบคลาสไม่ใช่ตัวชี้ 'ธรรมดา' (หรือคำที่เหมาะสมอาจเป็นอะไรก็ได้) นี่คือรหัสที่ฉันพยายาม วิ่ง:

#include <iostream>;

using namespace std;

int main() {
  int myVar,
      *myPointer;

  myVar = 8;
  myPointer = &myVar;

  cout << "delete-ing pointers " << endl;
  cout << "Memory address: " << myPointer << endl;

  // Seems I can't *just* delete it, as it triggers an error 
  delete myPointer;
  cout << "myPointer: " << myPointer << endl;
  // Error: a.out(14399) malloc: *** error for object 0x7fff61e537f4:
  // pointer being freed was not allocated
  // *** set a breakpoint in malloc_error_break to debug
  // Abort trap: 6

  // Using the new keyword befor deleting it works, but
  // does it really frees up the space? 
  myPointer = new int;
  delete myPointer;
  cout << "myPointer: " << myPointer << endl;
  // myPointer continues to store a memory address.

  // Using NULL before deleting it, seems to work. 
  myPointer = NULL;
  delete myPointer;
  cout << "myPointer: " << myPointer << endl;
  // myPointer returns 0.

}

ดังนั้นคำถามของฉันคือ:

  1. ทำไมเคสแรกถึงใช้ไม่ได้? ดูเหมือนจะตรงไปตรงมาที่สุดในการใช้และลบตัวชี้? ข้อผิดพลาดแจ้งว่าหน่วยความจำไม่ได้รับการจัดสรร แต่ "cout" ส่งกลับที่อยู่
  2. ในตัวอย่างที่สองข้อผิดพลาดไม่ได้ถูกทริกเกอร์ แต่การสร้างค่าของ myPointer ยังคงส่งคืนที่อยู่หน่วยความจำ?
  3. # 3 ได้ผลจริงหรือ? ดูเหมือนจะใช้ได้ผลสำหรับฉันตัวชี้ไม่ได้จัดเก็บที่อยู่อีกต่อไปนี่เป็นวิธีที่เหมาะสมในการลบตัวชี้หรือไม่

ขออภัยสำหรับคำถามที่ยาวต้องการทำให้ชัดเจนที่สุดและขอย้ำอีกครั้งว่าฉันมีประสบการณ์ในการเขียนโปรแกรมน้อยดังนั้นหากมีใครสามารถตอบคำถามนี้ได้โดยใช้เงื่อนไขของคนธรรมดาก็จะได้รับการชื่นชมอย่างมาก!


16
เหตุผลที่คุณไม่เห็นตัวอย่างแรกเพราะมันผิด เพียง แต่สิ่งที่คุณdelete newนอกจากนี้ยังไม่จำเป็นสำหรับตัวชี้ในการตั้งค่าตัวเองเป็น NULL หลังจากที่คุณลบ หากคุณต้องการความปลอดภัยที่นั่นให้ใช้ตัวชี้อัจฉริยะซึ่งช่วยให้หน่วยความจำว่างและให้ข้อผิดพลาดเมื่อคุณพยายามเข้าถึงเมื่อพวกเขาไม่ได้ถืออะไรบางอย่าง
chris

อืมโอเคฉันไม่แน่ใจว่าตัวชี้อัจฉริยะคืออะไร แต่ฉันจะพิจารณาขอบคุณ!
leopic

1
สรุปพวกเขาทำตามที่ฉันอธิบายไว้ เพื่อที่จะถือสิ่งใหม่คุณเรียกresetและมันก็ปลดปล่อยสิ่งเก่า หากต้องการให้ฟรีโดยไม่ต้องเปลี่ยนคุณโทรrelease. เมื่อมันอยู่นอกขอบเขตมันจะถูกทำลายและอาจทำให้หน่วยความจำว่างขึ้นอยู่กับประเภทของมัน std::unique_ptrมีไว้สำหรับเจ้าของเพียงคนเดียว std::shared_ptrปลดปล่อยเมื่อเจ้าของคนสุดท้ายหยุดเป็นเจ้าของทรัพยากร นอกจากนี้ยังปลอดภัยยกเว้น หากคุณจัดสรรทรัพยากรด้วยทรัพยากรแล้วพบข้อยกเว้นทรัพยากรจะได้รับการปลดปล่อยอย่างเหมาะสม
ริส

คำตอบ:


168

1 & 2

myVar = 8; //not dynamically allocated. Can't call delete on it.
myPointer = new int; //dynamically allocated, can call delete on it.

ตัวแปรแรกถูกจัดสรรบนสแตก คุณสามารถเรียกลบเฉพาะในหน่วยความจำที่คุณจัดสรรแบบไดนามิก (บนฮีป) โดยใช้ตัวnewดำเนินการ

3.

  myPointer = NULL;
  delete myPointer;

ข้างต้นไม่ได้ทำอะไรเลย คุณไม่ได้ปลดปล่อยอะไรเลยเนื่องจากตัวชี้ชี้ไปที่ NULL


สิ่งต่อไปนี้ไม่ควรทำ:

myPointer = new int;
myPointer = NULL; //leaked memory, no pointer to above int
delete myPointer; //no point at all

คุณชี้ไปที่ NULL ทิ้งหน่วยความจำที่รั่วไหลออกมา (int ใหม่ที่คุณจัดสรร) คุณควรปลดปล่อยหน่วยความจำที่คุณชี้ไป ไม่มีวิธีเข้าถึงที่จัดสรรnew intอีกต่อไปจึงทำให้หน่วยความจำรั่วไหล


วิธีที่ถูกต้อง:

myPointer = new int;
delete myPointer; //freed memory
myPointer = NULL; //pointed dangling ptr to NULL

วิธีที่ดีกว่า:

หากคุณใช้ C ++ อย่าใช้พอยน์เตอร์ดิบ ใช้ตัวชี้อัจฉริยะแทนซึ่งสามารถจัดการสิ่งเหล่านี้ให้คุณได้โดยมีค่าใช้จ่ายเพียงเล็กน้อย C ++ 11 มาพร้อมกับหลาย


13
<pedantry> "บนสแต็ก" คือรายละเอียดการใช้งานซึ่ง C ++ หลีกเลี่ยงการกล่าวถึงอย่างชัดเจน คำที่ถูกต้องกว่าคือ "พร้อมระยะเวลาการจัดเก็บอัตโนมัติ" (C ++ 11, 3.7.3) </
pedantry

4
ขอบคุณฉันเลือกคำตอบของคุณสำหรับ a) อธิบายสิ่งที่ผิดและ b) ให้แนวทางปฏิบัติที่ดีที่สุดขอบคุณมาก!
leopic

6
@Tqn ไม่ถูกต้อง deallocatesdelete myPointer *myPointerถูกต้อง. แต่myPointerยังคงชี้ไปที่ตำแหน่งหน่วยความจำที่ได้รับอิสระและไม่ควรใช้เนื่องจากเป็น UB จะไม่สามารถเข้าถึงได้หลังจากสิ้นสุดขอบเขตหากเป็นตัวแปรท้องถิ่นในตอนแรก
Anirudh Ramanathan

2
@DarkCthulhu ขอบคุณ! (ตามตัวอักษร) เรียนรู้บางสิ่งnewทุกวัน (ฉันวิเศษ!)
Tqn

1
@AmelSalibasic หน่วยความจำที่เชื่อมโยงกับตัวแปรบนสแต็กจะถูกปล่อยให้ว่างเพียงครั้งเดียวที่อยู่นอกขอบเขต การกำหนดเพื่อNULLป้องกันไม่ให้เรานำไปใช้ในทางที่ผิดในภายหลัง
Anirudh Ramanathan

24

ฉันเชื่อว่าคุณไม่เข้าใจวิธีการทำงานของพอยน์เตอร์
เมื่อคุณมีตัวชี้ที่ชี้ไปที่หน่วยความจำมีสามสิ่งที่คุณต้องเข้าใจ:
- มี "สิ่งที่ชี้" โดยตัวชี้ (หน่วยความจำ)
- ที่อยู่หน่วยความจำนี้
- ตัวชี้บางตัวไม่จำเป็นต้องมีการลบหน่วยความจำ: คุณเท่านั้น จำเป็นต้องลบหน่วยความจำที่จัดสรรแบบไดนามิก (ใช้newตัวดำเนินการ)

ลองนึกภาพ:

int *ptr = new int; 
// ptr has the address of the memory.
// at this point, the actual memory doesn't have anything.
*ptr = 8;
// you're assigning the integer 8 into that memory.
delete ptr;
// you are only deleting the memory.
// at this point the pointer still has the same memory address (as you could
//   notice from your 2nd test) but what inside that memory is gone!

เมื่อคุณทำ

ptr = NULL;
// you didn't delete the memory
// you're only saying that this pointer is now pointing to "nowhere".
// the memory that was pointed by this pointer is now lost.

C ++ ช่วยให้คุณพยายามdeleteชี้ไปที่ตัวชี้nullแต่มันไม่ได้ทำอะไรเลยเพียง แต่ไม่ให้ข้อผิดพลาด


2
ขอบคุณสิ่งนี้มีประโยชน์มากฉันคิดว่าฉันต้องการลบพอยน์เตอร์ทั้งหมดไม่ทราบว่ามีไว้สำหรับตัวชี้ใหม่เท่านั้นขอบคุณ
leopic

13

พอยน์เตอร์จะคล้ายกับตัวแปรปกติตรงที่คุณไม่จำเป็นต้องลบออก พวกเขาจะถูกลบออกจากหน่วยความจำเมื่อสิ้นสุดการเรียกใช้ฟังก์ชันและ / หรือสิ้นสุดโปรแกรม

อย่างไรก็ตามคุณสามารถใช้พอยน์เตอร์เพื่อจัดสรร 'บล็อก' ของหน่วยความจำได้เช่นนี้:

int *some_integers = new int[20000]

สิ่งนี้จะจัดสรรพื้นที่หน่วยความจำสำหรับจำนวนเต็ม 20000 มีประโยชน์เนื่องจาก Stack มีขนาดที่ จำกัด และคุณอาจต้องการยุ่งเกี่ยวกับ 'ints' จำนวนมากโดยไม่มีข้อผิดพลาดสแตกล้น

เมื่อใดก็ตามที่คุณโทรใหม่คุณควรจะ 'ลบ' ในตอนท้ายของโปรแกรมของคุณเพราะมิฉะนั้นคุณจะได้รับหน่วยความจำรั่วและพื้นที่หน่วยความจำที่จัดสรรบางส่วนจะไม่ถูกส่งคืนเพื่อให้โปรแกรมอื่นใช้ เพื่อทำสิ่งนี้:

delete [] some_integers;

หวังว่าจะช่วยได้


1
ฉันต้องการเพิ่มว่าหน่วยความจำที่จัดสรรจะถูกส่งคืนเพื่อให้โปรแกรมอื่นใช้ แต่หลังจากที่โปรแกรมของคุณทำงานเสร็จแล้วเท่านั้น
sk4l

7

มีกฎใน C ++ คือสำหรับทุกใหม่มีการลบ

  1. ทำไมเคสแรกถึงใช้ไม่ได้? ดูเหมือนจะตรงไปตรงมาที่สุดในการใช้และลบตัวชี้? ข้อผิดพลาดแจ้งว่าหน่วยความจำไม่ได้รับการจัดสรร แต่ "cout" ส่งกลับที่อยู่

ใหม่ไม่เคยเรียก ดังนั้นที่อยู่ที่ cout พิมพ์คือที่อยู่ของตำแหน่งหน่วยความจำของ myVar หรือค่าที่กำหนดให้ myPointer ในกรณีนี้ โดยเขียน:

myPointer = &myVar;

คุณพูด:

myPointer = ที่อยู่ของที่จัดเก็บข้อมูลใน myVar

  1. ในตัวอย่างที่สองข้อผิดพลาดไม่ได้ถูกทริกเกอร์ แต่การสร้างค่าของ myPointer ยังคงส่งคืนที่อยู่หน่วยความจำ?

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

  1. # 3 ได้ผลจริงหรือ? ดูเหมือนจะใช้ได้ผลสำหรับฉันตัวชี้ไม่ได้จัดเก็บที่อยู่อีกต่อไปนี่เป็นวิธีที่เหมาะสมในการลบตัวชี้หรือไม่

NULL เท่ากับ 0 คุณลบ 0 คุณจึงลบอะไรเลย และเป็นตรรกะที่พิมพ์ 0 เพราะคุณทำ:

myPointer = NULL;

ซึ่งเท่ากับ:

myPointer = 0;

4
  1. คุณกำลังพยายามลบตัวแปรที่จัดสรรบนสแตก คุณไม่สามารถทำได้
  2. การลบพอยน์เตอร์ไม่ได้ทำลายตัวชี้จริง ๆ แล้วมีเพียงแค่หน่วยความจำที่ครอบครองเท่านั้นที่จะถูกส่งกลับไปยังระบบปฏิบัติการ คุณสามารถเข้าถึงได้จนกว่าหน่วยความจำจะถูกใช้สำหรับตัวแปรอื่นหรือถูกปรับเปลี่ยน ดังนั้นจึงเป็นการดีที่จะตั้งค่าตัวชี้เป็น NULL (0) หลังจากลบ
  3. การลบตัวชี้ NULL ไม่ได้ลบอะไรเลย

2
int value, *ptr;

value = 8;
ptr = &value;
// ptr points to value, which lives on a stack frame.
// you are not responsible for managing its lifetime.

ptr = new int;
delete ptr;
// yes this is the normal way to manage the lifetime of
// dynamically allocated memory, you new'ed it, you delete it.

ptr = nullptr;
delete ptr;
// this is illogical, essentially you are saying delete nothing.

1
นอกจากนี้โปรดดูการบรรยายในสแต็กเฟรมyoutube.com/watch?v=bjObm0hxIYYและyoutube.com/watch?v=Rxvv9krECNwในพอยน์เตอร์
Casper Beyer
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.