การจัดสรรหน่วยความจำแบบไดนามิกในภาษา C: malloc(), calloc()

⚡ สรุปอย่างชาญฉลาด

การจัดสรรหน่วยความจำแบบไดนามิกในภาษา C จะสำรองและปล่อยหน่วยความจำฮีปในระหว่างการทำงานโดยใช้ฟังก์ชัน malloc(), calloc(), realloc() และ free() จากไลบรารี stdlib.h ทำให้โปรแกรมสามารถกำหนดขนาดของอาร์เรย์และโครงสร้างได้อย่างยืดหยุ่นเมื่อไม่ทราบความต้องการก่อนการทำงาน

  • 🧠 สแต็ก vs ฮีป: ตัวแปรพื้นฐานใช้สแต็กอัตโนมัติ ในขณะที่หน่วยความจำแบบไดนามิกจะอยู่บนฮีปและถูกจัดการด้วยตนเองผ่านตัวชี้
  • 📦 มอลลอค(): ฟังก์ชันนี้จะสงวนบล็อกหน่วยความจำที่ยังไม่ได้กำหนดค่าไว้หนึ่งบล็อก ขนาดตามที่ร้องขอ และส่งคืนพอยเตอร์ว่างเปล่า หรือค่า NULL เมื่อหน่วยความจำไม่เพียงพอ
  • 🔢 โทรล็อค(): จัดสรรบล็อกหลายบล็อกสำหรับอาร์เรย์ ตั้งค่าทุกไบต์เป็นศูนย์ และส่งคืนพอยเตอร์ไปยังบล็อกแรก
  • 🔄 realloc(): ปรับขนาดบล็อกที่มีอยู่ให้ใหญ่ขึ้นหรือเล็ลง โดยคงเนื้อหาเดิมไว้ในตำแหน่งใหม่ที่เป็นไปได้
  • 🧹 ฟรี(): ส่งคืนค่าหน่วยความจำกลับไปยังฮีป; ทุกครั้งที่มีการเรียกใช้ malloc() หรือ calloc() จะต้องมีการเรียกใช้ free() ที่สอดคล้องกันเพื่อป้องกันการรั่วไหลของหน่วยความจำ
  • 🤖 ความช่วยเหลือจาก AI: ผู้ช่วย AI และ GitHub Copilot ตรวจสอบโค้ดการจัดสรรฉบับร่าง และระบุการเรียกใช้ free() ที่ขาดหายไปและการรั่วไหลของข้อมูล

การจัดสรรหน่วยความจำแบบไดนามิกใน C

การจัดการหน่วยความจำใน C ทำงานอย่างไร

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

ตัวอย่างเช่น ตัวแปร float โดยทั่วไปจะใช้พื้นที่ 4 ไบต์ (ขึ้นอยู่กับแพลตฟอร์ม) เมื่อประกาศ เราสามารถตรวจสอบข้อมูลนี้ได้โดยใช้ตัวดำเนินการ sizeof ดังแสดงในตัวอย่างด้านล่าง

#include <stdio.h>
int main() { float x; printf("The size of float is %d bytes", sizeof(x)); return 0;}

การส่งออกจะได้รับ:

 The size of float is 4 bytes

นอกจากนี้ อาร์เรย์ที่มีขนาดที่ระบุจะถูกจัดสรรในบล็อกหน่วยความจำที่อยู่ติดกัน แต่ละบล็อกมีขนาดสำหรับหนึ่งองค์ประกอบ:

#include <stdio.h>
int main() { float arr[10];
printf("The size of the float array with 10 element is %d", sizeof(arr)); return 0;}

ผลลัพธ์คือ:

 The size of the float array with 10 element is 40

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

การจัดสรรหน่วยความจำแบบไดนามิกใน C

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

ตอนนี้คุณสามารถสร้างและทำลายอาร์เรย์ขององค์ประกอบแบบไดนามิกขณะรันไทม์ได้โดยไม่มีปัญหาใดๆ โดยสรุป การจัดการหน่วยความจำอัตโนมัติใช้สแต็ก และการจัดสรรหน่วยความจำแบบไดนามิก C ใช้ฮีป

ที่ ไลบรารีมีฟังก์ชันที่รับผิดชอบในการจัดการหน่วยความจำแบบไดนามิก

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

เรามาหารือเกี่ยวกับฟังก์ชันข้างต้นกับแอปพลิเคชันกันดีกว่า

ฟังก์ชัน malloc() ในภาษาซี

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

ไวยากรณ์ของฟังก์ชัน malloc():

ptr = (cast_type *) malloc (byte_size);

ที่นี่

  • ptr เป็นตัวชี้ของ cast_type
  • ฟังก์ชัน C malloc() ส่งคืนตัวชี้ไปยังหน่วยความจำที่จัดสรรขนาด byte_size

ตัวอย่างของ malloc():

Example: ptr = (int *) malloc (50)

เมื่อดำเนินการคำสั่งนี้สำเร็จ พื้นที่หน่วยความจำ 50 ไบต์จะถูกสงวนไว้ ที่อยู่ของไบต์แรกของพื้นที่สงวนถูกกำหนดให้กับตัวชี้ ptr ประเภท int

ลองพิจารณาอีกตัวอย่างหนึ่ง:

#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

ฟังก์ชัน C malloc()

  1. โปรดสังเกตว่ามีการใช้ sizeof(*ptr) แทน sizeof(int) เพื่อให้โค้ดมีความเสถียรมากขึ้นเมื่อมีการแปลงชนิดของการประกาศ *ptr ไปเป็นชนิดข้อมูลอื่นในภายหลัง
  2. การจัดสรรอาจล้มเหลวหากหน่วยความจำไม่เพียงพอ ในกรณีนี้จะส่งกลับตัวชี้ NULL ดังนั้นคุณควรรวมโค้ดเพื่อตรวจสอบตัวชี้ NULL
  3. โปรดจำไว้ว่าหน่วยความจำที่จัดสรรนั้นต่อเนื่องกันและสามารถถือเป็นอาร์เรย์ได้ เราสามารถใช้เลขคณิตตัวชี้เพื่อเข้าถึงองค์ประกอบของอาร์เรย์แทนที่จะใช้เครื่องหมายวงเล็บ [ ] เราแนะนำให้ใช้ + เพื่ออ้างถึงองค์ประกอบของอาร์เรย์ เนื่องจากการใช้การเพิ่มค่า ++ หรือ += จะเปลี่ยนที่อยู่ที่เก็บโดย ตัวชี้.

ฟังก์ชัน Malloc() ยังสามารถใช้ได้กับชนิดข้อมูลอักขระและชนิดข้อมูลที่ซับซ้อน เช่น โครงสร้างอีกด้วย

ฟังก์ชั่น free() ใน C

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

ฟังก์ชัน free() ถูกใช้เพื่อปล่อย/ยกเลิกการจัดสรรหน่วยความจำในภาษา C การปล่อยหน่วยความจำในโปรแกรมของคุณจะทำให้มีหน่วยความจำว่างมากขึ้นสำหรับการใช้งานในภายหลัง

ตัวอย่างเช่น:

#include <stdio.h>
int main() {
int* ptr = malloc(10 * sizeof(*ptr));
if (ptr != NULL){
  *(ptr + 2) = 50;
  printf("Value of the 2nd integer is %d",*(ptr + 2));
}
free(ptr);
}

Output:

 Value of the 2nd integer is 50

ฟังก์ชัน calloc() ในภาษาซี

ฟังก์ชัน C calloc() ย่อมาจาก contiguous allocation ฟังก์ชันนี้ใช้เพื่อจัดสรรหน่วยความจำหลายบล็อก เป็นฟังก์ชันการจัดสรรหน่วยความจำแบบไดนามิกซึ่งใช้เพื่อจัดสรรหน่วยความจำให้กับโครงสร้างข้อมูลที่ซับซ้อน เช่น อาร์เรย์และโครงสร้าง

ฟังก์ชัน Malloc() ใช้เพื่อจัดสรรพื้นที่หน่วยความจำบล็อกเดียว ในขณะที่ calloc() ในภาษา C ใช้เพื่อจัดสรรพื้นที่หน่วยความจำหลายบล็อก แต่ละบล็อกที่จัดสรรโดยฟังก์ชัน calloc() มีขนาดเท่ากัน

ไวยากรณ์ของฟังก์ชัน calloc()

ptr = (cast_type *) calloc (n, size);

ข้อความข้างต้นใช้เพื่อจัดสรรบล็อกหน่วยความจำที่มีขนาดเท่ากัน

หลังจากจัดสรรพื้นที่หน่วยความจำแล้ว ไบต์ทั้งหมดจะถูกเตรียมใช้งานให้เป็นศูนย์

ตัวชี้ซึ่งขณะนี้อยู่ที่ไบต์แรกของพื้นที่หน่วยความจำที่จัดสรรจะถูกส่งกลับ

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

ตัวอย่างของ calloc():

โปรแกรมด้านล่างนี้คำนวณผลรวมของลำดับเลขคณิต

#include <stdio.h>
    int main() {
        int i, * ptr, sum = 0;
        ptr = calloc(10, sizeof(int));
        if (ptr == NULL) {
            printf("Error! memory not allocated.");
            exit(0);
        }
        printf("Building and calculating the sequence sum of the first 10 terms \ n ");
        for (i = 0; i < 10; ++i) { * (ptr + i) = i;
            sum += * (ptr + i);
        }
        printf("Sum = %d", sum);
        free(ptr);
        return 0;
    }

ผลลัพธ์:

Building and calculating the sequence sum of the first 10 terms
Sum = 45

calloc() กับ malloc(): ความแตกต่างที่สำคัญ

ต่อไปนี้เป็นความแตกต่างที่สำคัญระหว่าง malloc() กับ calloc() ใน C:

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

บล็อกหน่วยความจำที่จัดสรรโดย calloc() ใน C จะถูกเตรียมใช้งานเป็นศูนย์เสมอในขณะที่อยู่ในฟังก์ชัน malloc() ใน C โดยจะมีค่าขยะอยู่เสมอ

ฟังก์ชัน realloc() ใน C

การใช้ฟังก์ชัน realloc() ในภาษา C ช่วยให้คุณสามารถเพิ่มขนาดหน่วยความจำให้กับหน่วยความจำที่จัดสรรไว้แล้วได้ ฟังก์ชันนี้จะขยายบล็อกปัจจุบันโดยคงเนื้อหาเดิมไว้ realloc() ในภาษา C ย่อมาจาก reallocation of memory (การจัดสรรหน่วยความจำใหม่)

realloc() ยังสามารถใช้เพื่อลดขนาดของหน่วยความจำที่จัดสรรไว้ก่อนหน้านี้ได้

ไวยากรณ์ของฟังก์ชัน realloc():

ptr = realloc (ptr,newsize);

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

ตัวอย่างของการจัดสรรใหม่ ():

#include <stdio.h>
int main () {
   char *ptr;
   ptr = (char *) malloc(10);
   strcpy(ptr, "Programming");
   printf(" %s,  Address = %u\n", ptr, ptr);

   ptr = (char *) realloc(ptr, 20); //ptr is reallocated with new size
   strcat(ptr, " In 'C'");
   printf(" %s,  Address = %u\n", ptr, ptr);
   free(ptr);
   return 0;
}

เมื่อใดก็ตามที่ realloc() ใน C ส่งผลให้การดำเนินการไม่สำเร็จ ก็จะส่งคืนตัวชี้ค่าว่าง และข้อมูลก่อนหน้านี้ก็จะได้รับการปลดปล่อยด้วยเช่นกัน

อาร์เรย์แบบไดนามิกใน C

อาร์เรย์แบบไดนามิกในภาษา C ช่วยให้จำนวนองค์ประกอบเพิ่มขึ้นตามต้องการ อาร์เรย์แบบไดนามิกใน C ใช้กันอย่างแพร่หลายในอัลกอริทึมของวิทยาการคอมพิวเตอร์

ในโปรแกรมต่อไปนี้ เราได้สร้างและปรับขนาดอาร์เรย์ไดนามิกใน C

#include <stdio.h>
    int main() {
        int * arr_dynamic = NULL;
        int elements = 2, i;
        arr_dynamic = calloc(elements, sizeof(int)); //Array with 2 integer blocks
        for (i = 0; i < elements; i++) arr_dynamic[i] = i;
        for (i = 0; i < elements; i++) printf("arr_dynamic[%d]=%d\n", i, arr_dynamic[i]);
        elements = 4;
        arr_dynamic = realloc(arr_dynamic, elements * sizeof(int)); //reallocate 4 elements
        printf("After realloc\n");
        for (i = 2; i < elements; i++) arr_dynamic[i] = i;
        for (i = 0; i < elements; i++) printf("arr_dynamic[%d]=%d\n", i, arr_dynamic[i]);
        free(arr_dynamic);
    }

ผลลัพธ์ของโปรแกรม C Dynamic Array ที่หน้าจอ:

 
arr_dynamic[0]=0
arr_dynamic[1]=1
After realloc
arr_dynamic[0]=0
arr_dynamic[1]=1
arr_dynamic[2]=2
arr_dynamic[3]=3

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

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

พอยเตอร์ที่ค้างอยู่ (dangling pointer) ยังคงถือที่อยู่ของหน่วยความจำที่ฟังก์ชัน free() ได้ปล่อยไปแล้ว การเข้าถึงค่าที่พอยเตอร์ชี้อยู่จะทำให้เกิดพฤติกรรมที่ไม่แน่นอน ดังนั้นเพื่อความปลอดภัย ควรตั้งค่า NULL ให้กับพอยเตอร์ทันทีหลังจากปล่อยหน่วยความจำแล้ว

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

ไม่ การเรียก free() สองครั้งบนแอดเดรสเดียวกันเรียกว่า double free ซึ่งจะทำให้เกิดพฤติกรรมที่ไม่แน่นอนหรือโปรแกรมหยุดทำงาน การตั้งค่าตัวชี้เป็น NULL หลังจากเรียก free() ครั้งแรก จะทำให้การเรียก free() ครั้งต่อๆ ไปไม่มีผลเสียใดๆ

realloc(NULL, size) ทำงานเหมือนกับ malloc(size) ทุกประการ และจะจัดสรรบล็อกใหม่ ส่วน realloc(ptr, 0) จะปล่อยบล็อกนั้น ดังนั้นควรเก็บพอยเตอร์ที่ส่งคืนมาแทนที่จะเขียนทับพอยเตอร์เดิมเสมอ

เครื่องมือต่างๆ เช่น Valgrind, AddressSanitizer และ Dr. Memory track การจัดสรรและการรายงานทุกบล็อกที่ไม่เคยถูกปล่อยคืน พร้อมด้วยบรรทัดที่แน่นอน helping คุณตรวจพบการรั่วไหลก่อนที่จะวางจำหน่าย

ใช่แล้ว ผู้ช่วยเขียนโค้ด AI จะร่างลำดับการเรียกใช้ malloc(), calloc() และ free() จากข้อความแจ้งเตือน อธิบายข้อผิดพลาดของตัวชี้ และแจ้งเตือนเมื่อขาดการเรียกใช้ free() ควรคอมไพล์และทดสอบโค้ดที่สร้างขึ้นก่อนที่จะเชื่อถือเสมอ

นักบิน GitHub แนะนำให้เพิ่มบล็อกการจัดสรร การตรวจสอบค่า NULL และการเรียกใช้ฟังก์ชัน free() ที่ตรงกันจากความคิดเห็น ตรวจสอบความสมบูรณ์แต่ละครั้งเพื่อหาการรั่วไหลและขนาดที่ถูกต้องก่อนที่จะเผยแพร่

สรุปโพสต์นี้ด้วย: