การจัดสรรหน่วยความจำแบบไดนามิก – Dynamic Memory Allocation
เผยแพร่เมื่อ: 21 มิถุนายน 2025 | หมวดหมู่: C | โดย: GlowCode Team

ในภาษา C การจัดสรรหน่วยความจำแบบคงที่ (static allocation) เช่นอาร์เรย์ขนาดคงที่ มีข้อจำกัดเพราะต้องกำหนดขนาดล่วงหน้า แต่ในโปรแกรมจริง ขนาดข้อมูลอาจเปลี่ยนแปลงได้ (เช่นรายการของผู้ใช้ที่เพิ่มขึ้น) การจัดสรรหน่วยความจำแบบไดนามิก (dynamic allocation) ช่วยให้เราจัดสรรหน่วยความจำในเวลารันไทม์ (runtime) ผ่านฟังก์ชันจาก ซึ่งช่วยให้โปรแกรมยืดหยุ่นและประหยัดหน่วยความจำมากขึ้น
ข้อกำหนดเบื้องต้น
- ความรู้พื้นฐานจากบทก่อนหน้า: พอยเตอร์ (Pointers) (ถ้ายังไม่ได้เรียน แนะนำอ่านก่อน เพราะการจัดสรรแบบไดนามิกต้องใช้พอยเตอร์)
- รวม <stdlib.h> สำหรับฟังก์ชัน malloc, calloc, realloc, free
วัตถุประสงค์
- เข้าใจการใช้ malloc, calloc, realloc, free
- เรียนรู้การจัดการการรั่วไหลของหน่วยความจำ (memory leaks) และพอยเตอร์ที่ค้างคา (dangling pointers)
- ใช้ GlowCode เพื่อตรวจสอบการจัดสรรหน่วยความจำ
ฟังก์ชันพื้นฐานสำหรับการจัดสรรแบบไดนามิก
- malloc(size_t size): จัดสรร memory ขนาด size bytes และ return pointer ไปยังพื้นที่นั้น (void*) ถ้าล้มเหลว return NULL
- calloc(size_t num, size_t size): คล้าย malloc แต่ initialize memory เป็น 0 (เหมาะสำหรับ arrays)
- realloc(void* ptr, size_t new_size): เปลี่ยนขนาด memory ที่จัดสรรแล้ว (อาจย้ายตำแหน่ง)
- free(void* ptr): คืน memory ให้ระบบ (ป้องกัน memory leak)
📌 หมายเหตุประสิทธิภาพ: malloc และ calloc มี overhead จากการค้นหาบล็อกว่างใน heap ดังนั้นหลีกเลี่ยงการเรียกบ่อยๆ ในลูป ถ้าเป็นไปได้ ให้จัดสรรทีละก้อนใหญ่
💡 เคล็ดลับ: เสมอตรวจสอบว่าการจัดสรรสำเร็จหรือไม่ด้วย if (ptr == NULL) { /* จัดการข้อผิดพลาด */ } เพื่อป้องกัน segmentation fault
ตัวอย่างที่ 1: การใช้ malloc และ free พื้นฐาน
#include <stdlib.h>
int main(void) {
int* ptr; // พอยเตอร์สำหรับเก็บที่อยู่
int n = 5; // ขนาดที่ต้องการ (ค่า runtime)
// จัดสรรหน่วยความจำสำหรับ int 5 ตัว (ขนาด 5 * sizeof(int))
ptr = (int*)malloc(n * sizeof(int));
if (ptr == NULL) {
printf("การจัดสรรหน่วยความจำล้มเหลว!\n");
return 1; // ออกจากโปรแกรมด้วยข้อผิดพลาด
}
// ใช้งานหน่วยความจำ
for (int i = 0; i < n; i++) {
ptr[i] = i * 10; // เขียนค่า
printf("%d ", ptr[i]);
}
printf("\n");
// คืนหน่วยความจำ
free(ptr);
ptr = NULL; // ตั้งค่าเป็น NULL เพื่อป้องกันพอยเตอร์ที่ค้างคา
return 0;
}
ผลลัพธ์:
0 10 20 30 40
⚠️ คำเตือน: ลืม free() จะทำให้เกิดการรั่วไหลของหน่วยความจำ – หน่วยความจำที่ไม่ถูกคืนให้ระบบ สะสมไปเรื่อยๆ จนโปรแกรมช้าหรือ crash ในโปรแกรมที่รันนาน
ตัวอย่างที่ 2: calloc vs malloc – การเริ่มต้นค่า
#include <stdlib.h>
int main(void) {
int* malloc_ptr = (int*)malloc(3 * sizeof(int)); // ❌ ไม่เริ่มต้นค่า – ค่าขยะ
int* calloc_ptr = (int*)calloc(3, sizeof(int)); // ✅ เริ่มต้นค่าเป็น 0
printf("ค่าจาก malloc (ขยะ):\n");
for (int i = 0; i < 3; i++) {
printf("%d ", malloc_ptr[i]);
}
printf("\n");
printf("ค่าจาก calloc (เป็นศูนย์):\n");
for (int i = 0; i < 3; i++) {
printf("%d ", calloc_ptr[i]);
}
printf("\n");
free(malloc_ptr);
free(calloc_ptr);
return 0;
}
ผลลัพธ์ (ตัวอย่างค่าขยะ):
ค่าจาก malloc (ขยะ):
-1482707056 32766 0
ค่าจาก calloc (เป็นศูนย์):
0 0 0
🎯 เคล็ดลับขั้นสูง: ใช้ calloc สำหรับอาร์เรย์เพื่อหลีกเลี่ยงพฤติกรรมที่ไม่กำหนดจากค่าขยะ แต่ calloc ช้ากว่า malloc เล็กน้อยเพราะต้องตั้งค่าเป็น 0
ตัวอย่างที่ 3: realloc – การเปลี่ยนขนาดหน่วยความจำ
#include <stdlib.h>
int main(void) {
int* ptr = (int*)malloc(3 * sizeof(int)); // ขนาดเริ่มต้น 3
if (ptr == NULL) return 1;
// เติมค่าเริ่มต้น
for (int i = 0; i < 3; i++) ptr[i] = i + 1;
// เปลี่ยนขนาดเป็น 5 (อาจคัดลอกข้อมูลเก่าไปที่ใหม่)
ptr = (int*)realloc(ptr, 5 * sizeof(int));
if (ptr == NULL) return 1;
// เติมค่าเพิ่ม
ptr[3] = 4;
ptr[4] = 5;
// แสดงผล
for (int i = 0; i < 5; i++) {
printf("%d ", ptr[i]);
}
printf("\n");
free(ptr);
return 0;
}
ผลลัพธ์:
1 2 3 4 5
⚠️ ข้อผิดพลาดทั่วไป: ถ้า realloc ล้มเหลว มันคืนค่า NULL แต่พอยเตอร์เดิมยังชี้ไปที่หน่วยความจำเก่า ถ้าลืม assign กลับ จะเสียพอยเตอร์เดิมและเกิดการรั่วไหลของหน่วยความจำ ดังนั้นใช้พอยเตอร์ชั่วคราว:
int* new_ptr = realloc(old_ptr, new_size);
if (new_ptr == NULL) { free(old_ptr); /* จัดการข้อผิดพลาด */ }
old_ptr = new_ptr;
ข้อผิดพลาดทั่วไปและแนวปฏิบัติที่ดีที่สุด
⚠️ คำเตือนร้ายแรง: การจัดสรรแบบไดนามิก ถ้าจัดการผิด อาจนำไปสู่:
- การรั่วไหลของหน่วยความจำ: ลืม free() – หน่วยความจำสูญเปล่า
- พอยเตอร์ที่ค้างคา: free แล้วยังใช้พอยเตอร์นั้น – พฤติกรรมที่ไม่กำหนด (อาจ crash)
- Heap Overflow: จัดสรรมากเกินจน heap เต็ม
- Double Free: free พอยเตอร์เดียวกันสองครั้ง – การเสียหายของข้อมูล
ตัวอย่างการรั่วไหลของหน่วยความจำ
// ❌ รั่วไหล
int* ptr = malloc(100);
ptr = NULL; // สูญเสียพอยเตอร์ – ไม่สามารถ free ได้
// ✅ ถูกต้อง
int* ptr = malloc(100);
free(ptr);
ptr = NULL;
📊 หมายเหตุ: การรั่วไหลของหน่วยความจำไม่ทำให้โปรแกรม crash ทันที แต่สะสมในแอปที่รันนาน เช่นเซิร์ฟเวอร์
พอยเตอร์ที่ค้างคา
// ❌ พอยเตอร์ที่ค้างคา
int* ptr = malloc(sizeof(int));
*ptr = 42;
free(ptr);
printf(“%d”, *ptr); // ⚠️ พฤติกรรมที่ไม่กำหนด!
// ✅ ตั้งค่า NULL หลัง free
free(ptr);
ptr = NULL;
แนวปฏิบัติที่ดีที่สุด:
- ใช้ Valgrind หรือ GlowCode เพื่อตรวจจับการรั่วไหลในการพัฒนา
- จัดสรรเฉพาะที่จำเป็น และ free ทันทีเมื่อไม่ใช้
- สำหรับการจัดสรรขนาดใหญ่ ใช้การจัดการข้อผิดพลาดที่ดี
- ใน C11 ใช้ aligned_alloc สำหรับหน่วยความจำที่จัดตำแหน่ง (เช่น SIMD)
💡 ข้อเท็จจริงสนุก: Heap ใน C จัดการโดยไลบรารีรันไทม์ (เช่น glibc ใน Linux) malloc อาจใช้ алгоритмы เช่น buddy system เพื่อหาบล็อกว่างอย่างรวดเร็ว
การรวมกับ GlowCode
การวิเคราะห์หน่วยความจำแบบไดนามิก
GlowCode ยอดเยี่ยมสำหรับตรวจสอบการจัดสรรแบบไดนามิก:
- Memory Profiler:
- ติดตามการจัดสรร/reallocations/frees
- ตรวจจับการรั่วไหลโดยแสดงบล็อกที่ไม่ถูกคืนเมื่อโปรแกรมจบ
- แสดงภาพการใช้งาน heap ตามเวลา
- Performance Impact:
- วัดเวลาใน malloc/free (ถ้าบ่อยเกินอาจเป็นจุดคอขวด)
ลองทำดู:
// Profile this leaky code with GlowCode
#include <stdlib.h>
void leaky_function() {
int* ptr = (int*)malloc(1000 * sizeof(int)); // ❌ ไม่มี free!
}
int main(void) {
for (int i = 0; i < 100; i++) {
leaky_function(); // รั่วไหล 1000 ints ทุกครั้ง!
}
return 0;
}
คำถาม: ใช้ GlowCode เพื่อดูว่ามีการรั่วไหลของหน่วยความจำกี่ bytes? และแก้ไขอย่างไร?
สรุป
ในบทนี้เราได้เรียนรู้:
✅ ฟังก์ชัน malloc, calloc, realloc, free
✅ การจัดการหน่วยความจำแบบไดนามิก
✅ ข้อผิดพลาดทั่วไป เช่นการรั่วไหลและพอยเตอร์ที่ค้างคา
✅ แนวปฏิบัติที่ดีที่สุดสำหรับการจัดสรรที่ปลอดภัย
✅ การใช้ GlowCode เพื่อวิเคราะห์หน่วยความจำ
ขั้นตอนถัดไป
- ฝึกเขียนโปรแกรมที่ใช้อาร์เรย์แบบไดนามิก (เช่น linked list เบื้องต้น)
- ทดลองสร้างการรั่วไหลแล้วใช้ GlowCode ตรวจจับ
- เตรียมสำหรับบทเรียนถัดไป: File I/O (ซึ่งจะใช้หน่วยความจำแบบไดนามิกสำหรับบัฟเฟอร์)
ทรัพยากรเพิ่มเติม
เคล็ดลับประสิทธิภาพ:
- หลีกเลี่ยง realloc บ่อย – ใช้ over-allocation (เช่นเพิ่มขนาดสองเท่าทุกครั้ง)
- ใช้ custom allocators สำหรับแอปประสิทธิภาพสูง

