สมมติว่าฉันมีรหัสต่อไปนี้:
void* my_alloc (size_t size)
{
return new char [size];
}
void my_free (void* ptr)
{
delete [] ptr;
}
ปลอดภัยหรือไม่? หรือต้องแคptr
สchar*
ก่อนที่จะลบ?
สมมติว่าฉันมีรหัสต่อไปนี้:
void* my_alloc (size_t size)
{
return new char [size];
}
void my_free (void* ptr)
{
delete [] ptr;
}
ปลอดภัยหรือไม่? หรือต้องแคptr
สchar*
ก่อนที่จะลบ?
คำตอบ:
ขึ้นอยู่กับ "ปลอดภัย" โดยปกติจะใช้งานได้เนื่องจากข้อมูลถูกจัดเก็บพร้อมกับตัวชี้เกี่ยวกับการจัดสรรเองดังนั้น deallocator จึงสามารถส่งคืนข้อมูลไปยังตำแหน่งที่ถูกต้องได้ ในแง่นี้มัน "ปลอดภัย" ตราบใดที่ผู้จัดสรรของคุณใช้แท็กขอบเขตภายใน (หลายคนทำ)
อย่างไรก็ตามตามที่กล่าวไว้ในคำตอบอื่น ๆ การลบตัวชี้ที่เป็นโมฆะจะไม่เรียกตัวทำลายซึ่งอาจเป็นปัญหาได้ ในแง่นั้นมันไม่ "ปลอดภัย"
ไม่มีเหตุผลที่ดีที่จะทำสิ่งที่คุณกำลังทำในแบบที่คุณกำลังทำอยู่ หากคุณต้องการเขียนฟังก์ชันการจัดสรรตำแหน่งของคุณเองคุณสามารถใช้เทมเพลตฟังก์ชันเพื่อสร้างฟังก์ชันด้วยประเภทที่ถูกต้อง เหตุผลที่ดีในการทำเช่นนั้นคือการสร้างตัวจัดสรรพูลซึ่งอาจมีประสิทธิภาพอย่างมากสำหรับบางประเภท
ดังที่กล่าวไว้ในคำตอบอื่น ๆ นี่คือพฤติกรรมที่ไม่ได้กำหนดใน C ++ โดยทั่วไปเป็นการดีที่จะหลีกเลี่ยงพฤติกรรมที่ไม่ได้กำหนดแม้ว่าหัวข้อนั้นจะซับซ้อนและเต็มไปด้วยความคิดเห็นที่ขัดแย้งกันก็ตาม
sizeof(T*) == sizeof(U*)
สำหรับทุกคนT,U
ชี้ให้เห็นว่าควรมีvoid *
การใช้งานตัวเก็บขยะแบบไม่มีเทมเพลต 1 รายการ แต่แล้วเมื่อ gc ต้องลบ / ว่างตัวชี้ก็เกิดคำถามนี้ขึ้น เพื่อให้มันใช้งานได้คุณอาจต้องใช้ lambda function destructor wrapper (urgh) หรือคุณจะต้องมีประเภทของ "type as data" แบบไดนามิกซึ่งอนุญาตให้กลับไปกลับมาระหว่างประเภทและสิ่งที่เก็บได้
การลบผ่านตัวชี้โมฆะไม่ได้กำหนดโดยมาตรฐาน C ++ - ดูหัวข้อ 5.3.5 / 3:
ในทางเลือกแรก (ลบอ็อบเจ็กต์) หากประเภทคงที่ของตัวถูกดำเนินการแตกต่างจากประเภทไดนามิกประเภทคงที่จะต้องเป็นคลาสพื้นฐานของประเภทไดนามิกของตัวถูกดำเนินการและประเภทคงที่จะต้องมีตัวทำลายเสมือนหรือไม่ได้กำหนดลักษณะการทำงาน . ในทางเลือกที่สอง (ลบอาร์เรย์) หากชนิดไดนามิกของวัตถุที่จะลบแตกต่างจากประเภทคงที่พฤติกรรมจะไม่ได้กำหนดไว้
และเชิงอรรถ:
นี่หมายความว่าไม่สามารถลบอ็อบเจ็กต์โดยใช้ตัวชี้ประเภท void * เนื่องจากไม่มีอ็อบเจ็กต์ประเภทโมฆะ
.
NULL
สร้างความแตกต่างให้กับการจัดการหน่วยความจำของแอปพลิเคชันหรือไม่
ไม่ใช่ความคิดที่ดีและไม่ใช่สิ่งที่คุณจะทำใน C ++ คุณกำลังสูญเสียข้อมูลประเภทของคุณโดยไม่มีเหตุผล
ตัวทำลายของคุณจะไม่ถูกเรียกบนอ็อบเจ็กต์ในอาร์เรย์ที่คุณกำลังลบเมื่อคุณเรียกมันสำหรับประเภทที่ไม่ใช่ดั้งเดิม
คุณควรแทนที่ใหม่ / ลบ
การลบโมฆะ * อาจจะทำให้หน่วยความจำของคุณว่างอย่างถูกต้องโดยบังเอิญ แต่มันผิดเพราะผลลัพธ์ไม่ได้กำหนดไว้
หากฉันไม่ทราบด้วยเหตุผลบางอย่างคุณจำเป็นต้องจัดเก็บตัวชี้ของคุณในความว่างเปล่า * จากนั้นให้เป็นอิสระคุณควรใช้ malloc และฟรี
การลบตัวชี้ที่เป็นโมฆะเป็นสิ่งที่อันตรายเนื่องจากตัวทำลายจะไม่ถูกเรียกตามค่าที่ชี้ไปจริง ซึ่งอาจส่งผลให้หน่วยความจำ / ทรัพยากรรั่วไหลในแอปพลิเคชันของคุณ
คำถามไม่สมเหตุสมผล ความสับสนของคุณส่วนหนึ่งอาจเกิดจากภาษาที่ผู้คนมักใช้กับdelete
:
คุณใช้delete
เพื่อทำลายวัตถุที่จัดสรรแบบไดนามิก ในการทำเช่นนั้นคุณจะสร้างนิพจน์การลบโดยมีตัวชี้ไปที่วัตถุนั้น คุณไม่เคย "ลบตัวชี้" สิ่งที่คุณทำจริงๆคือ "ลบวัตถุที่ระบุโดยที่อยู่"
ตอนนี้เราเห็นแล้วว่าทำไมคำถามถึงไม่มีเหตุผล: ตัวชี้โมฆะไม่ใช่ "ที่อยู่ของวัตถุ" เป็นเพียงที่อยู่โดยไม่มีความหมายใด ๆ มันอาจจะมาจากที่อยู่ของวัตถุที่เกิดขึ้นจริง แต่ข้อมูลที่หายไปเพราะมันถูกเข้ารหัสในประเภทของตัวชี้เดิม วิธีเดียวในการเรียกคืนตัวชี้วัตถุคือการโยนตัวชี้โมฆะกลับไปที่ตัวชี้วัตถุ (ซึ่งผู้เขียนต้องรู้ว่าตัวชี้หมายถึงอะไร) void
ตัวเองเป็นประเภทที่ไม่สมบูรณ์ดังนั้นจึงไม่มีประเภทของวัตถุและไม่สามารถใช้ตัวชี้โมฆะเพื่อระบุวัตถุได้ (วัตถุถูกระบุร่วมกันตามประเภทและที่อยู่)
delete
อาจเป็นค่าตัวชี้ว่างตัวชี้ไปยังวัตถุที่ไม่ใช่อาร์เรย์ที่สร้างขึ้นโดยนิพจน์ใหม่ก่อนหน้านี้หรือตัวชี้ไปที่ วัตถุย่อยที่แสดงถึงคลาสพื้นฐานของอ็อบเจ็กต์ดังกล่าวหากไม่เป็นเช่นนั้นพฤติกรรมจะไม่ถูกกำหนด " ดังนั้นหากคอมไพเลอร์ยอมรับรหัสของคุณโดยไม่มีการวินิจฉัยก็ไม่มีอะไรนอกจากบั๊กในคอมไพเลอร์ ...
delete void_pointer
ไฟล์. เป็นพฤติกรรมที่ไม่ได้กำหนด โปรแกรมเมอร์ไม่ควรเรียกใช้พฤติกรรมที่ไม่ได้กำหนดแม้ว่าการตอบสนองจะดูเหมือนว่าจะทำในสิ่งที่โปรแกรมเมอร์ต้องการให้ทำก็ตาม
ถ้าคุณต้องทำสิ่งนี้จริงๆทำไมไม่ตัดคนกลาง ( new
และdelete
ตัวดำเนินการ) ออกแล้วโทรไปที่ส่วนกลางoperator new
และoperator delete
โดยตรง? (แน่นอนว่าหากคุณกำลังพยายามที่จะใช้เครื่องมือnew
และdelete
ตัวดำเนินการจริงคุณควรนำไปใช้operator new
และoperator delete
ใหม่)
void* my_alloc (size_t size)
{
return ::operator new(size);
}
void my_free (void* ptr)
{
::operator delete(ptr);
}
หมายเหตุที่แตกต่างmalloc()
, operator new
พ่นstd::bad_alloc
ในความล้มเหลว (หรือเรียกว่าnew_handler
หากมีการลงทะเบียน)
เนื่องจากถ่านไม่มีตรรกะตัวทำลายพิเศษ สิ่งนี้จะใช้ไม่ได้
class foo
{
~foo() { printf("huzza"); }
}
main()
{
foo * myFoo = new foo();
delete ((void*)foo);
}
ไม่ได้รับการเรียกตัวนักแสดง
ถ้าคุณต้องการใช้ void * ทำไมคุณไม่ใช้แค่ malloc / ฟรีล่ะ? ใหม่ / ลบเป็นมากกว่าการจัดการหน่วยความจำ โดยทั่วไป new / delete เรียก constructor / destructor และมีหลายสิ่งเกิดขึ้น หากคุณใช้เพียงชนิดในตัว (เช่นถ่าน *) และลบออกผ่านโมฆะ * มันจะใช้ได้ แต่ก็ยังไม่แนะนำ บรรทัดล่างคือใช้ malloc / ฟรีหากคุณต้องการใช้ void * มิฉะนั้นคุณสามารถใช้ฟังก์ชันเทมเพลตเพื่อความสะดวกของคุณ
template<typename T>
T* my_alloc (size_t size)
{
return new T [size];
}
template<typename T>
void my_free (T* ptr)
{
delete [] ptr;
}
int main(void)
{
char* pChar = my_alloc<char>(10);
my_free(pChar);
}
หลายคนแสดงความคิดเห็นแล้วบอกว่าไม่ไม่ปลอดภัยที่จะลบตัวชี้ที่เป็นโมฆะ ฉันเห็นด้วยกับสิ่งนั้น แต่ฉันก็ต้องการเพิ่มว่าหากคุณกำลังทำงานกับพอยน์เตอร์โมฆะเพื่อจัดสรรอาร์เรย์ที่อยู่ติดกันหรือสิ่งที่คล้ายกันคุณสามารถทำได้new
เพื่อให้คุณสามารถใช้งานdelete
ได้อย่างปลอดภัย (ด้วยอะแฮ่ม , งานพิเศษเล็กน้อย). สิ่งนี้ทำได้โดยการจัดสรรตัวชี้โมฆะไปยังพื้นที่หน่วยความจำ (เรียกว่า 'อารีน่า') จากนั้นส่งตัวชี้ไปยังอารีน่าให้ใหม่ ดูในส่วนนี้ในc ++ คำถามที่พบบ่อย นี่เป็นแนวทางทั่วไปในการใช้งานพูลหน่วยความจำใน C ++
แทบจะไม่มีเหตุผลที่จะทำเช่นนี้
ครั้งแรกของทั้งหมดถ้าคุณไม่ทราบชนิดของข้อมูลและสิ่งที่คุณรู้ก็คือว่ามันเป็นvoid*
แล้วคุณจริงๆก็ควรจะได้รับการรักษาข้อมูลที่เป็น typeless หยดของข้อมูลไบนารี ( unsigned char*
) และการใช้งานmalloc
/ free
การจัดการกับมัน . บางครั้งสิ่งนี้จำเป็นสำหรับสิ่งต่างๆเช่นข้อมูลรูปคลื่นและสิ่งที่คล้ายกันซึ่งคุณต้องส่งพvoid*
อยน์เตอร์ไปยัง C apis ไม่เป็นไร.
ถ้าคุณทำทราบชนิดของข้อมูล (คือมันมี ctor / dtor) แต่ด้วยเหตุผลบางอย่างที่คุณสิ้นสุดกับvoid*
ตัวชี้ (สำหรับเหตุผลที่สิ่งที่คุณมี) แล้วคุณไม่ควรโยนมันกลับไปที่ประเภทที่คุณรู้ว่ามันจะ เป็นและเรียกdelete
มัน
ฉันใช้ void *, (aka Unknown types) ในเฟรมเวิร์กของฉันในขณะที่อยู่ในการสะท้อนรหัสและความคลุมเครืออื่น ๆ และจนถึงขณะนี้ฉันไม่มีปัญหาใด ๆ (หน่วยความจำรั่วการละเมิดการเข้าถึง ฯลฯ ) จากคอมไพเลอร์ใด ๆ เฉพาะคำเตือนเนื่องจากการทำงานไม่ได้มาตรฐาน
เหมาะสมอย่างยิ่งที่จะลบสิ่งที่ไม่รู้จัก (โมฆะ *) ตรวจสอบให้แน่ใจว่าตัวชี้เป็นไปตามหลักเกณฑ์เหล่านี้มิฉะนั้นอาจไม่สมเหตุสมผล:
1) ตัวชี้ที่ไม่รู้จักต้องไม่ชี้ไปที่ชนิดที่มีตัวถอดรหัสเล็กน้อยดังนั้นเมื่อร่ายเป็นตัวชี้ที่ไม่รู้จักก็ไม่ควรถูกลบ ลบเฉพาะตัวชี้ที่ไม่รู้จักหลังจากส่งกลับไปยังประเภท ORIGINAL
2) อินสแตนซ์ถูกอ้างถึงเป็นตัวชี้ที่ไม่รู้จักในสแตกที่ถูกผูกไว้หรือหน่วยความจำที่ถูกผูกไว้หรือไม่ หากตัวชี้ที่ไม่รู้จักอ้างถึงอินสแตนซ์บนสแต็กก็ไม่ควรลบ!
3) คุณเป็นบวก 100% หรือไม่ที่ตัวชี้ที่ไม่รู้จักเป็นพื้นที่หน่วยความจำที่ถูกต้อง? ไม่แล้วไม่ควรลบ!
โดยรวมแล้วมีงานโดยตรงน้อยมากที่สามารถทำได้โดยใช้ประเภทตัวชี้ที่ไม่รู้จัก (โมฆะ *) อย่างไรก็ตามในทางอ้อม void * เป็นสินทรัพย์ที่ยอดเยี่ยมสำหรับนักพัฒนา C ++ ที่จะต้องพึ่งพาเมื่อจำเป็นต้องมีความคลุมเครือของข้อมูล
หากคุณต้องการแค่บัฟเฟอร์ให้ใช้ malloc / free หากคุณต้องใช้ new / delete ให้พิจารณาคลาส Wrapper เล็กน้อย:
template<int size_ > struct size_buffer {
char data_[ size_];
operator void*() { return (void*)&data_; }
};
typedef sized_buffer<100> OpaqueBuffer; // logical description of your sized buffer
OpaqueBuffer* ptr = new OpaqueBuffer();
delete ptr;
สำหรับกรณีเฉพาะของถ่าน
ถ่านเป็นชนิดที่อยู่ภายในที่ไม่มีตัวทำลายพิเศษ ดังนั้นข้อโต้แย้งที่รั่วไหลจึงเป็นเรื่องที่น่าสงสัย
sizeof (ถ่าน) มักจะเป็นหนึ่งดังนั้นจึงไม่มีอาร์กิวเมนต์การจัดตำแหน่งเช่นกัน ในกรณีของแพลตฟอร์มที่หายากโดยที่ sizeof (ถ่าน) ไม่ใช่อย่างใดอย่างหนึ่งพวกเขาจะจัดสรรหน่วยความจำที่จัดแนวให้เพียงพอสำหรับถ่านของพวกเขา ดังนั้นอาร์กิวเมนต์การจัดตำแหน่งก็เป็นข้อสงสัยเช่นกัน
malloc / free จะเร็วกว่าในกรณีนี้ แต่คุณเสียค่ามาตรฐาน :: bad_alloc และต้องตรวจสอบผลลัพธ์ของ malloc การเรียกใช้ตัวดำเนินการใหม่และลบทั่วโลกอาจจะดีกว่าเพราะมันข้ามคนกลาง
new
ถูกกำหนดให้โยนจริง นี่ไม่เป็นความจริง. มันขึ้นอยู่กับสวิตช์คอมไพเลอร์และคอมไพเลอร์ ดูตัวอย่าง/GX[-] enable C++ EH (same as /EHsc)
สวิตช์MSVC2019 นอกจากนี้ในระบบฝังตัวหลายคนเลือกที่จะไม่จ่ายภาษีประสิทธิภาพสำหรับข้อยกเว้น C ++ ดังนั้นประโยคที่ขึ้นต้นด้วย "But you forfeit std :: bad_alloc ... " จึงน่าสงสัย