1. บทนำ

เมื่อเริ่มเขียนโปรแกรมด้วยภาษา C ในตอนแรกมักจะจัดการหน่วยความจำโดยใช้อาร์เรย์เป็นส่วนใหญ่ อย่างไรก็ตาม เมื่อโปรแกรมซับซ้อนมากขึ้น ก็มีสถานการณ์ที่ต้องการจัดการหน่วยความจำได้อย่างยืดหยุ่นมากขึ้น ในสถานการณ์เช่นนั้น “การจัดสรรหน่วยความจำแบบไดนามิก” จะเข้ามามีบทบาทสำคัญ malloc เป็นฟังก์ชันที่เป็นตัวแทน ซึ่งช่วยให้สามารถจัดสรรหน่วยความจำที่จำเป็นในระหว่างการทำงานของโปรแกรมแบบไดนามิกได้

ยกตัวอย่างเช่น malloc เปรียบเสมือน “อาหารที่ทำหลังจากสั่ง” ส่วนหน่วยความจำที่กำหนดไว้ล่วงหน้า (อาร์เรย์) อาจเปรียบได้กับ “อาหารแบบบุฟเฟต์” กระบวนการพื้นฐานคือการ “สั่ง” หน่วยความจำเท่าที่คุณต้องการโดยใช้ malloc และเมื่อใช้งานเสร็จแล้วก็ “นำจานออกไป (คืนหน่วยความจำด้วยฟังก์ชัน free)” ในบทความนี้ เราจะมาดูรายละเอียดเกี่ยวกับ malloc กัน

2. malloc คืออะไร?

malloc เป็นตัวย่อของ “memory allocation” (การจัดสรรหน่วยความจำ) และเป็นฟังก์ชันสำหรับจัดสรรหน่วยความจำแบบไดนามิกในภาษา C ในระหว่างการทำงานของโปรแกรม จะจัดสรรหน่วยความจำตามขนาดที่ระบุและส่งคืนที่อยู่เริ่มต้นของหน่วยความจำนั้น ซึ่งช่วยให้สามารถใช้หน่วยความจำเท่าที่จำเป็นระหว่างการทำงานของโปรแกรม และสามารถจัดการหน่วยความจำได้อย่างยืดหยุ่น ซึ่งเป็นเรื่องยากสำหรับอาร์เรย์ที่มีขนาดคงที่

ในโค้ดจริง จะใช้ malloc ดังนี้

int *array = (int*)malloc(10 * sizeof(int));

ในตัวอย่างนี้ ได้จัดสรรอาร์เรย์ประเภทจำนวนเต็มจำนวน 10 ตัว ที่นี่สิ่งสำคัญคือ malloc ส่งคืนที่อยู่เริ่มต้นของหน่วยความจำที่จัดสรร ดังนั้น ประเภทอาจไม่ตรงกันตามปกติ ดังนั้นจึงเป็นเรื่องปกติที่จะทำการแปลงประเภท (cast) ให้เป็นประเภทที่ต้องการ ข้างต้นใช้ (int*) เพื่อแปลงเป็นพอยน์เตอร์ประเภทจำนวนเต็ม

3. วิธีการใช้งานพื้นฐานของ malloc

ตอนนี้ เรามาดูวิธีการใช้งาน malloc อย่างละเอียดกันหน่อย ก่อนอื่น รูปแบบการใช้งานพื้นฐานของ malloc เป็นดังนี้

void* malloc(size_t size);

ฟังก์ชัน malloc รับอาร์กิวเมนต์คือขนาดของหน่วยความจำที่ต้องการจัดสรร (จำนวนไบต์) จากนั้นจะจัดสรรพื้นที่หน่วยความจำขนาดนั้น และหากสำเร็จ จะส่งคืนที่อยู่เริ่มต้นของพื้นที่นั้น ประเภทที่ส่งคืนคือ void* ซึ่งเป็นพอยน์เตอร์ทั่วไปที่สามารถแปลงเป็นประเภทใดก็ได้ ตัวอย่างเช่น ใช้ดังนี้

int *array = (int*)malloc(10 * sizeof(int));

ที่นี่ sizeof(int) ใช้เพื่อหาขนาดของหน่วยความจำที่จะจัดสรร การทำเช่นนี้ช่วยให้สามารถจัดสรรหน่วยความจำขนาดที่ถูกต้องได้ในสภาพแวดล้อมที่แตกต่างกัน เมื่อใช้หน่วยความจำที่จัดสรรแล้ว สิ่งสำคัญคือต้องคืนหน่วยความจำนั้นโดยใช้ฟังก์ชัน free หากไม่คืน หน่วยความจำรั่วไหล (memory leak) จะเกิดขึ้น

4. ความสำคัญของการคืนหน่วยความจำด้วย free()

การจัดสรรหน่วยความจำแบบไดนามิกสะดวกจริง แต่มีข้อควรระวังหนึ่งข้อ คือ ต้องไม่ลืมคืนหน่วยความจำที่จัดสรรไว้ หากละเลย จะเกิดหน่วยความจำรั่วไหล ทำให้โปรแกรมสิ้นเปลืองหน่วยความจำจำนวนมาก

หน่วยความจำที่จัดสรรด้วย malloc จะถูกคืนด้วย free() ดังนี้

free(array);

หน่วยความจำที่ไม่ถูกคืนจะยังคงอยู่ในระบบจนกว่าโปรแกรมจะสิ้นสุด ซึ่งอาจกลายเป็นปัญหาสำคัญสำหรับโปรแกรมที่ทำงานเป็นเวลานาน หากเปรียบเทียบก็เหมือนกับการยืมจานด้วย malloc และหากไม่คืนจานให้เรียบร้อยด้วย free ห้องครัวก็จะเต็มไปด้วยจาน

5. ความสำคัญของการตรวจสอบ NULL

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

int *array = (int*)malloc(100000000 * sizeof(int));
if (array == NULL) {
    // การจัดการเมื่อจัดสรรหน่วยความจำล้มเหลว
    printf("Memory allocation failed.
");
    return 1;
}

การตรวจสอบเช่นนี้ช่วยให้สามารถจัดการข้อผิดพลาดเมื่อการจัดสรรหน่วยความจำล้มเหลว การเพิ่มมาตรการความปลอดภัยเล็กน้อยในโค้ดจะช่วยป้องกันปัญหาใหญ่ที่อาจเกิดขึ้นในภายหลังได้

6. ความแตกต่างระหว่าง malloc และ calloc

ในภาษา C มีฟังก์ชันอื่น ๆ สำหรับการจัดสรรหน่วยความจำแบบไดนามิก นอกเหนือจาก malloc หนึ่งในนั้นคือ calloc malloc และ calloc มีความคล้ายคลึงกันมาก แต่มีความแตกต่างที่สำคัญบางประการ malloc จะจัดสรรหน่วยความจำตามขนาดที่ระบุเท่านั้น และเนื้อหาภายในจะไม่ได้รับการเริ่มต้น ในทางกลับกัน calloc จะจัดสรรหน่วยความจำพร้อมทั้งเริ่มต้นหน่วยความจำที่จัดสรรทั้งหมดด้วยค่าศูนย์

วิธีการใช้งาน calloc

int *array = (int*)calloc(10, sizeof(int));

โค้ดนี้จะจัดสรรอาร์เรย์ประเภทจำนวนเต็มจำนวน 10 ตัว และเริ่มต้นแต่ละองค์ประกอบด้วยค่าศูนย์ ความแตกต่างหลักจาก malloc คือ calloc รับอาร์กิวเมนต์สองตัวคือ “จำนวนองค์ประกอบ” และ “ขนาดขององค์ประกอบ” เหตุผลที่โครงสร้างนี้สะดวกคือ ช่วยให้สามารถจัดสรรหน่วยความจำได้อย่างชัดเจนยิ่งขึ้นเมื่อจัดการข้อมูลที่มีหลายองค์ประกอบ เช่น อาร์เรย์

การเลือกใช้ฟังก์ชันใดขึ้นอยู่กับสถานการณ์ แต่หากต้องการการเริ่มต้นค่า calloc จะสะดวกกว่า ในทางกลับกัน หากไม่ต้องการการเริ่มต้นค่า หรือให้ความสำคัญกับประสิทธิภาพ malloc จะเหมาะสมกว่า

7. ตัวอย่างการใช้งานจริง: การจัดสรรสตริงแบบไดนามิกด้วย malloc

ในส่วนนี้ เราจะมาดูตัวอย่างการจัดสรรหน่วยความจำแบบไดนามิกสำหรับสตริงโดยใช้ malloc เมื่อจัดการสตริงในภาษา C โดยปกติจะใช้อาร์เรย์ที่มีขนาดคงที่ อย่างไรก็ตาม หากความยาวของสตริงไม่ทราบจนกว่าจะถึงเวลาทำงาน หรือต้องการจัดการสตริงแบบไดนามิก malloc จะมีประโยชน์

char *str = (char*)malloc(50 * sizeof(char));
if (str == NULL) {
    printf("Memory allocation failed.
");
    return 1;
}
sprintf(str, "Hello, World!");
printf("%s
", str);
free(str);

โค้ดนี้จะจัดสรรหน่วยความจำขนาด 50 ตัวอักษรแบบไดนามิก และเก็บสตริง “Hello, World!” ลงในพื้นที่นั้น อย่าลืมคืนหน่วยความจำด้วยฟังก์ชัน free หลังจากใช้งาน การใช้ malloc ช่วยให้สามารถจัดการหน่วยความจำได้อย่างยืดหยุ่น ซึ่งเป็นไปไม่ได้ด้วยอาร์เรย์ขนาดคงที่

8. การใช้งาน malloc กับโครงสร้าง (Struct)

ต่อไป เรามาดูตัวอย่างการใช้ malloc เพื่อจัดสรรหน่วยความจำแบบไดนามิกสำหรับโครงสร้าง (struct) โครงสร้างเป็นประเภทข้อมูลที่มีประสิทธิภาพซึ่งสามารถจัดการข้อมูลหลายประเภทพร้อมกันได้ และสามารถจัดการหน่วยความจำได้แบบไดนามิกเช่นกัน

typedef struct {
    int id;
    char *name;
} Person;

Person *p = (Person*)malloc(sizeof(Person));
if (p == NULL) {
    printf("Memory allocation failed.
");
    return 1;
}
p->name = (char*)malloc(50 * sizeof(char));
sprintf(p->name, "John Doe");
p->id = 1;

printf("ID: %d, Name: %s
", p->id, p->name);

free(p->name);
free(p);

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

9. ข้อผิดพลาดที่พบบ่อยในการใช้งาน malloc

เราจะพูดถึงข้อผิดพลาดที่ผู้เริ่มต้นมักทำเมื่อใช้ malloc ด้วย การหลีกเลี่ยงข้อผิดพลาดเหล่านี้จะช่วยให้สามารถเขียนโปรแกรมที่ปลอดภัยและมีประสิทธิภาพมากขึ้น

     

  1. ลืมคืนหน่วยความจำ
    หากลืมคืนหน่วยความจำที่จัดสรรแบบไดนามิกด้วย free() จะเกิดหน่วยความจำรั่วไหล ซึ่งอาจเป็นปัญหาได้โดยเฉพาะในโปรแกรมที่ทำงานเป็นเวลานาน ไม่ว่าโปรแกรมจะซับซ้อนเพียงใดก็ตาม ควรทำให้เป็นนิสัยในการคืนหน่วยความจำที่จัดสรรไว้เสมอ
  2.  

  3. ละเว้นการตรวจสอบ NULL
    มักจะลืมว่า NULL จะถูกส่งคืนหากการจัดสรรหน่วยความจำล้มเหลว ควรตรวจสอบค่า NULL ทันทีหลังจากการจัดสรรหน่วยความจำเสมอ และนำการจัดการข้อผิดพลาดมาใช้
  4.  

  5. เข้าถึงหน่วยความจำที่ไม่ได้เริ่มต้น
    หน่วยความจำที่จัดสรรด้วย malloc อยู่ในสถานะที่ไม่ได้เริ่มต้น หากพยายามใช้งานโดยตรง อาจทำให้เกิดพฤติกรรมที่ไม่คาดคิดได้ โดยเฉพาะอย่างยิ่งหากต้องการการเริ่มต้นค่า ควรพิจารณาใช้ calloc

10. สรุป

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

คำถามที่พบบ่อย (FAQ)

     

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

  3. ควรใช้ malloc หรือ calloc?
    หากไม่ต้องการการเริ่มต้นค่า malloc จะเหมาะสมกว่า หากต้องการเริ่มต้นหน่วยความจำด้วยค่าศูนย์ calloc จะเหมาะสมกว่า