ฟังก์ชัน malloc() ในไลบรารี C พร้อมตัวอย่าง
⚡ สรุปอย่างชาญฉลาด
ฟังก์ชัน malloc() ในภาษา C จะจองบล็อกหน่วยความจำบนฮีปในระหว่างการทำงานของโปรแกรม และส่งคืนพอยเตอร์แบบ void ไปยังไบต์แรกของบล็อกนั้น ทำให้โปรแกรมสามารถร้องขอพื้นที่จัดเก็บข้อมูลที่มีขนาดไม่ทราบแน่ชัดจนกว่าโค้ดจะทำงานจริง
malloc ใน C คืออะไร?
ฟังก์ชัน malloc() ย่อมาจากการจัดสรรหน่วยความจำ เป็นฟังก์ชันที่ใช้ในการจัดสรรบล็อกหน่วยความจำแบบไดนามิก จะสงวนพื้นที่หน่วยความจำตามขนาดที่ระบุและส่งกลับตัวชี้ null ที่ชี้ไปยังตำแหน่งหน่วยความจำ ตัวชี้ที่ส่งคืนมักจะเป็นประเภทโมฆะ หมายความว่าเราสามารถกำหนดฟังก์ชัน malloc ให้กับพอยน์เตอร์ใดก็ได้
วากยสัมพันธ์
ptr = (cast_type *) malloc (byte_size);
ที่นี่
- ptr เป็นตัวชี้ของ cast_type
- ฟังก์ชัน malloc ส่งคืนตัวชี้ไปยังหน่วยความจำที่จัดสรรเป็น byte_size
Example: ptr = (int *) malloc (50)
เมื่อดำเนินการคำสั่งนี้สำเร็จ พื้นที่หน่วยความจำ 50 ไบต์จะถูกสงวนไว้ ที่อยู่ของไบต์แรกของพื้นที่สงวนถูกกำหนดให้กับตัวชี้ ptr ประเภท int
พิจารณาอีกตัวอย่างหนึ่งของการนำ malloc ไปใช้งาน:
#include <stdlib.h> int main(){ int *ptr; ptr = malloc(15 * sizeof(*ptr)); /* a block of 15 integers */ if (ptr != NULL) { *(ptr + 5) = 480; /* assign 480 to sixth integer */ printf("Value of the 6th integer is %d",*(ptr + 5)); } }
Output:
Value of the 6th integer is 480
โปรดสังเกตว่ามีการใช้ sizeof(*ptr) แทน sizeof(int) เพื่อให้โค้ดมีความเสถียรมากขึ้นเมื่อมีการแปลงชนิดของการประกาศ *ptr ไปเป็นชนิดข้อมูลอื่นในภายหลัง
การจัดสรรอาจล้มเหลวหากหน่วยความจำไม่เพียงพอ ในกรณีนี้จะส่งกลับตัวชี้ NULL ดังนั้นคุณควรรวมโค้ดเพื่อตรวจสอบตัวชี้ NULL
โปรดจำไว้ว่าหน่วยความจำที่จัดสรรนั้นต่อเนื่องกันและสามารถมองได้ว่าเป็นอาร์เรย์ เราสามารถใช้ ตัวชี้ ใช้การคำนวณทางคณิตศาสตร์เพื่อเข้าถึงองค์ประกอบของอาร์เรย์แทนการใช้วงเล็บ [ ] เราแนะนำให้ใช้ + เพื่ออ้างถึงองค์ประกอบของอาร์เรย์ เนื่องจากการใช้การเพิ่มค่า ++ หรือ += จะเปลี่ยนที่อยู่ซึ่งเก็บโดยตัวชี้
ฟังก์ชัน Malloc ยังสามารถใช้ได้กับชนิดข้อมูลอักขระและชนิดข้อมูลที่ซับซ้อน เช่น โครงสร้างอีกด้วย
เหตุใดจึงต้องใช้ malloc() ในภาษา C?
อาร์เรย์แบบคงที่และตัวแปรทั่วไปจะมีขนาดคงที่เมื่อโปรแกรมถูกคอมไพล์ ซึ่งทำให้สิ้นเปลืองพื้นที่เมื่อความต้องการมีขนาดเล็กลง และจะล้มเหลวโดยสิ้นเชิงเมื่อความต้องการมีขนาดใหญ่ขึ้น ฟังก์ชัน malloc() แก้ปัญหานี้ได้โดยการขอหน่วยความจำในขณะรันไทม์ ดังนั้นโปรแกรมจึงสามารถกำหนดขนาดบัฟเฟอร์ให้ตรงกับปริมาณข้อมูลที่ได้รับ ความยืดหยุ่นในขณะรันไทม์นี้เป็นหัวใจสำคัญของ การจัดสรรหน่วยความจำแบบไดนามิก ใน C.
การจัดสรรหน่วยความจำแบบไดนามิกด้วย malloc() เป็นตัวเลือกที่เหมาะสมในหลายสถานการณ์ทั่วไป:
- ปริมาณข้อมูลป้อนเข้าจะไม่ทราบจนกว่าโปรแกรมจะเริ่มทำงาน เช่น การอ่านไฟล์หรือรายการที่ผู้ใช้ป้อนเข้ามา
- จำเป็นต้องใช้บล็อกขนาดใหญ่ซึ่งจะทำให้สแต็กที่มีจำกัดล้น เนื่องจากฟังก์ชัน malloc() ดึงข้อมูลจากฮีปที่มีขนาดใหญ่กว่ามาก
- ข้อมูลต้องมีอายุยืนยาวกว่าฟังก์ชันที่สร้างมันขึ้นมา เนื่องจากหน่วยความจำฮีปจะคงอยู่จนกว่าจะถูกปล่อยอย่างชัดเจน
- โครงสร้างและอาร์เรย์จำเป็นต้องขยายหรือหดตัวขณะที่โปรแกรมกำลังทำงาน
เนื่องจากบล็อกที่สงวนไว้จะถูกส่งกลับมาในรูปแบบของตัวชี้ จึงสามารถส่งต่อระหว่างฟังก์ชันได้อย่างสะดวกโดยไม่ต้องคัดลอกข้อมูลต้นฉบับ
วิธีการคืนหน่วยความจำที่จัดสรรโดย malloc()
หน่วยความจำที่จองโดยฟังก์ชัน malloc() จะอยู่บนฮีปและจะไม่ถูกปล่อยโดยอัตโนมัติ เมื่อโปรแกรมทำการจัดสรรหน่วยความจำโดยไม่ปล่อยหน่วยความจำ มันจะค่อยๆ ใช้หน่วยความจำที่มีอยู่ทั้งหมด ซึ่งเป็นข้อบกพร่องที่เรียกว่าหน่วยความจำรั่วไหล ฟังก์ชัน free() จะส่งคืนบล็อกที่เสร็จสมบูรณ์แล้วให้กับระบบ เพื่อให้สามารถนำพื้นที่นั้นกลับมาใช้ใหม่ได้
ฟังก์ชัน free() รับอาร์กิวเมนต์เพียงตัวเดียว คือพอยเตอร์ที่ฟังก์ชัน malloc() ส่งคืน และปล่อยบล็อกทั้งหมดในคราวเดียว:
int *ptr; ptr = malloc(10 * sizeof(int)); if (ptr != NULL) { /* use the allocated memory here */ free(ptr); /* release the block */ ptr = NULL; /* avoid a dangling pointer */ }
ปฏิบัติตามกฎเหล่านี้เพื่อจัดการหน่วยความจำแบบไดนามิกอย่างปลอดภัย:
- เรียกฟังก์ชัน free() เพียงครั้งเดียวสำหรับการจัดสรรหน่วยความจำ (malloc()) ที่สำเร็จแต่ละครั้ง การปลดปล่อยบล็อกเดียวกันสองครั้งจะทำให้เกิดพฤติกรรมที่ไม่แน่นอน
- หลังจากปล่อยหน่วยความจำแล้ว ให้ตั้งค่าตัวชี้เป็น NULL เพื่อป้องกันไม่ให้ถูกนำกลับมาใช้ใหม่เป็นตัวชี้ที่ชี้ไปยังตำแหน่งที่ไม่ถูกต้อง (dangling pointer)
- ห้ามใช้ free() กับพอยเตอร์ที่ malloc() ไม่ได้ส่งคืนมา รวมถึงพอยเตอร์ที่ถูกเลื่อนด้วยการคำนวณเลขคณิตของพอยเตอร์ด้วย
- จัดสรรหน่วยความจำทุกครั้งให้ตรงกับการปล่อยหน่วยความจำที่เหมาะสม ก่อนที่ตัวชี้จะอยู่นอกขอบเขตการใช้งาน
Keeping การจัดสรรและการปล่อยหน่วยความจำอย่างสมดุลเป็นหัวใจสำคัญของการจัดการหน่วยความจำด้วยตนเองในภาษาซี และช่วยป้องกันทั้งการรั่วไหลและการหยุดทำงานที่เกิดจากการนำหน่วยความจำที่ถูกปล่อยไปแล้วกลับมาใช้ใหม่
malloc() กับ calloc(): ความแตกต่างที่สำคัญ
malloc() และ การโทร () ทั้งสองฟังก์ชันใช้สำหรับจองหน่วยความจำฮีป แต่แตกต่างกันที่อาร์กิวเมนต์และวิธีการจัดการบล็อกใหม่ ฟังก์ชัน malloc() รับอาร์กิวเมนต์หนึ่งตัว คือจำนวนไบต์ทั้งหมด และปล่อยให้เนื้อหาไม่มีค่าเริ่มต้น ดังนั้นบล็อกจึงเก็บค่าขยะที่ไม่สามารถคาดเดาได้จนกว่าโปรแกรมจะเขียนลงไป ในขณะที่ฟังก์ชัน calloc() รับอาร์กิวเมนต์สองตัว คือจำนวนองค์ประกอบและขนาดของแต่ละองค์ประกอบ และจะล้างทุกไบต์ให้เป็นศูนย์ก่อนที่จะส่งคืนตัวชี้
ความแตกต่างในทางปฏิบัติสรุปได้เป็นสามประเด็นหลัก:
- การเริ่มต้นใช้งาน: calloc() จะกำหนดค่าหน่วยความจำให้เป็นศูนย์ ในขณะที่ malloc() จะไม่ทำเช่นนั้น ทำให้ malloc() เร็วกว่าเล็กน้อยเมื่อค่าต่างๆ จะถูกเขียนทับอยู่แล้ว
- เหตุผล: คุณเขียน malloc(n * size) แต่เขียน calloc(n, size) เพื่อจองอาร์เรย์เดียวกัน
- กรณีการใช้งาน: เลือกใช้ calloc() เมื่อต้องการบล็อกหน่วยความจำที่สะอาดและมีค่าเป็นศูนย์ และเลือกใช้ malloc() เมื่อต้องการความเร็วและจัดการการเริ่มต้นใช้งานด้วยตนเอง
ฟังก์ชันทั้งสองถูกประกาศไว้ใน stdlib.h ทั้งคู่ส่งคืนพอยเตอร์แบบ void หรือ NULL เมื่อเกิดข้อผิดพลาด และทั้งคู่คืนหน่วยความจำด้วยฟังก์ชัน free() เดียวกัน


