อาร์กิวเมนต์เดียวfree(void *)
(แนะนำใน Unix V7) มีข้อได้เปรียบที่สำคัญอีกประการหนึ่งจากสองอาร์กิวเมนต์ก่อนหน้านี้mfree(void *, size_t)
ซึ่งฉันไม่เคยเห็นที่นี่: หนึ่งอาร์กิวเมนต์free
ช่วยลดความซับซ้อนของAPI อื่น ๆที่ทำงานกับหน่วยความจำฮีปได้อย่างมาก ตัวอย่างเช่นหากfree
ต้องการขนาดของบล็อกหน่วยความจำก็strdup
จะต้องคืนค่าสองค่า (ตัวชี้ + ขนาด) แทนที่จะเป็นค่าเดียว (ตัวชี้) และ C ทำให้การส่งคืนหลายค่ายุ่งยากกว่าการส่งคืนค่าเดียว แทนการchar *strdup(char *)
ที่เราจะต้องเขียนหรืออื่น ๆchar *strdup(char *, size_t *)
struct CharPWithSize { char *val; size_t size}; CharPWithSize strdup(char *)
(ปัจจุบันตัวเลือกที่สองนั้นดูน่าดึงดูดทีเดียวเพราะเราทราบดีว่าสตริงที่ยุติด้วย NUL นั้นเป็น"จุดบกพร่องในการออกแบบที่หายนะที่สุดในประวัติศาสตร์ของการคำนวณ"แต่นั่นเป็นการพูดที่มองย้อนกลับไป ย้อนกลับไปในทศวรรษที่ 70 ความสามารถของ C ในการจัดการกับสตริงอย่างง่ายchar *
ๆ ถือเป็นข้อได้เปรียบที่กำหนดเหนือคู่แข่งเช่น Pascal และ Algol ) นอกจากนี้ไม่เพียงแค่strdup
ประสบปัญหานี้เท่านั้น แต่ยังส่งผลกระทบต่อทุกระบบหรือผู้ใช้กำหนด ฟังก์ชันที่จัดสรรหน่วยความจำฮีป
นักออกแบบ Unix ยุคแรก ๆ เป็นคนที่ฉลาดมากและมีหลายสาเหตุที่free
ดีกว่าmfree
โดยพื้นฐานแล้วฉันคิดว่าคำตอบสำหรับคำถามคือพวกเขาสังเกตเห็นสิ่งนี้และออกแบบระบบของพวกเขาตามนั้น ฉันสงสัยว่าคุณจะพบบันทึกโดยตรงเกี่ยวกับสิ่งที่เกิดขึ้นในหัวของพวกเขาในขณะที่พวกเขาตัดสินใจ แต่เราสามารถจินตนาการ
หลอกว่าคุณกำลังเขียนโปรแกรมประยุกต์ใน C เพื่อให้ทำงานบนระบบปฏิบัติการยูนิกซ์ V6 mfree
กับสองอาร์กิวเมนต์ คุณจัดการได้โอเคแล้ว แต่การติดตามขนาดตัวชี้เหล่านี้กลายเป็นเรื่องยุ่งยากมากขึ้นเรื่อย ๆ เนื่องจากโปรแกรมของคุณมีความทะเยอทะยานมากขึ้นและต้องการการใช้ตัวแปรที่จัดสรรฮีปมากขึ้นเรื่อย ๆ แต่คุณก็มีความคิดที่ยอดเยี่ยม: แทนที่จะคัดลอกสิ่งเหล่านี้size_t
ตลอดเวลาคุณสามารถเขียนฟังก์ชันยูทิลิตี้บางอย่างซึ่งซ่อนขนาดไว้ในหน่วยความจำที่จัดสรรไว้โดยตรง:
void *my_alloc(size_t size) {
void *block = malloc(sizeof(size) + size);
*(size_t *)block = size;
return (void *) ((size_t *)block + 1);
}
void my_free(void *block) {
block = (size_t *)block - 1;
mfree(block, *(size_t *)block);
}
และยิ่งคุณเขียนโค้ดโดยใช้ฟังก์ชันใหม่เหล่านี้มากเท่าไหร่ก็ยิ่งดูน่ากลัวมากขึ้นเท่านั้น ไม่เพียง แต่ทำให้โค้ดของคุณเขียนง่ายขึ้นเท่านั้น แต่ยังทำให้โค้ดของคุณเร็วขึ้น - สองสิ่งที่มักไม่ได้อยู่ด้วยกัน! ก่อนที่คุณจะส่งผ่านสิ่งเหล่านี้size_t
ไปทั่วทุกที่ซึ่งเพิ่มค่าใช้จ่ายของ CPU สำหรับการคัดลอกและหมายความว่าคุณต้องทำรีจิสเตอร์บ่อยขึ้น (โดยเฉพาะสำหรับอาร์กิวเมนต์ฟังก์ชันพิเศษ) และหน่วยความจำที่สูญเปล่า (เนื่องจากการเรียกฟังก์ชันซ้อนกันมักจะส่งผล ในหลายสำเนาของsize_t
การจัดเก็บในเฟรมสแตกต่างกัน) ในระบบใหม่ของคุณคุณยังต้องใช้หน่วยความจำในการจัดเก็บไฟล์size_t
แต่เพียงครั้งเดียวและไม่เคยคัดลอกที่ไหน สิ่งเหล่านี้อาจดูเหมือนประสิทธิภาพเล็กน้อย แต่โปรดทราบว่าเรากำลังพูดถึงเครื่องระดับไฮเอนด์ที่มี RAM 256 KiB
สิ่งนี้ทำให้คุณมีความสุข! ดังนั้นคุณจึงแบ่งปันเคล็ดลับเด็ด ๆ ของคุณกับผู้ชายมีเคราที่กำลังทำ Unix รุ่นถัดไป แต่มันไม่ได้ทำให้พวกเขามีความสุขมันทำให้พวกเขาเศร้า คุณจะเห็นว่าพวกเขาอยู่ในขั้นตอนการเพิ่มฟังก์ชันยูทิลิตี้ใหม่ ๆ เช่นstrdup
และพวกเขาตระหนักดีว่าคนที่ใช้เคล็ดลับเด็ด ๆ ของคุณจะไม่สามารถใช้ฟังก์ชันใหม่ของพวกเขาได้เนื่องจากฟังก์ชันใหม่ทั้งหมดใช้ตัวชี้ + ขนาดที่ยุ่งยาก API และนั่นก็ทำให้คุณเสียใจเช่นกันเพราะคุณรู้ว่าคุณจะต้องเขียนstrdup(char *)
ฟังก์ชันที่ดีด้วยตัวคุณเองในทุกโปรแกรมที่คุณเขียนแทนที่จะใช้เวอร์ชันของระบบ
แต่เดี๋ยวก่อน! นี่คือปี 1977 และความเข้ากันได้แบบย้อนหลังจะไม่ถูกคิดค้นขึ้นอีก 5 ปี! และนอกจากนี้ไม่มีใครจริงจังใช้สิ่ง "Unix" ที่คลุมเครือนี้ด้วยชื่อที่ไม่เป็นสี K&R ฉบับแรกกำลังจะไปถึงผู้จัดพิมพ์แล้ว แต่ก็ไม่มีปัญหา - มันบอกในหน้าแรกว่า "C ไม่มีการดำเนินการใด ๆ เพื่อจัดการกับวัตถุผสมโดยตรงเช่นสตริงอักขระ ... ไม่มีฮีป ... ". ณ จุดนี้ในประวัติศาสตร์string.h
และmalloc
เป็นส่วนขยายผู้ให้บริการ (!) ดังนั้นแนะนำ Bearded Man # 1 เราสามารถเปลี่ยนแปลงได้ตามที่เราต้องการ ทำไมเราไม่ประกาศผู้จัดสรรที่ยุ่งยากของคุณให้เป็นผู้จัดสรรอย่างเป็นทางการล่ะ
ไม่กี่วันต่อมา Bearded Man # 2 เห็น API ใหม่และบอกว่าเดี๋ยวก่อนรอก่อนดีกว่า แต่ก็ยังใช้จ่ายทั้งคำต่อการจัดสรรเพื่อจัดเก็บขนาด เขามองว่านี่เป็นสิ่งต่อไปของการดูหมิ่น คนอื่นมองเขาเหมือนเขาเป็นบ้าเพราะคุณจะทำอะไรได้อีก? คืนนั้นเขานอนดึกและคิดค้นตัวจัดสรรใหม่ที่ไม่ได้เก็บขนาดเลย แต่จะสรุปได้ทันทีโดยการใช้เวทมนตร์ดำบนค่าตัวชี้และเปลี่ยนมันในขณะที่ยังคง API ใหม่ไว้ API ใหม่หมายความว่าไม่มีใครสังเกตเห็นสวิตช์ แต่พวกเขาสังเกตเห็นว่าเช้าวันรุ่งขึ้นคอมไพเลอร์ใช้ RAM น้อยลง 10%
และตอนนี้ทุกคนมีความสุข: คุณได้รับโค้ดที่ง่ายต่อการเขียนและเร็วขึ้น Bearded Man # 1 จะเขียนง่าย ๆstrdup
ที่ผู้คนจะใช้งานได้จริงและ Bearded Man # 2 - มั่นใจว่าเขาได้รับเงินมาเล็กน้อย - - กลับไปmessing รอบด้วย quines จัดส่ง!
หรืออย่างน้อยนั่นคือสิ่งที่อาจเกิดขึ้นได้