เมื่อใดที่ฉันควรใช้ malloc ใน C และเมื่อใดที่ไม่ควรใช้


94

ฉันเข้าใจวิธีการทำงานของ malloc () คำถามของฉันคือฉันจะเห็นสิ่งนี้:

#define A_MEGABYTE (1024 * 1024)

char *some_memory;
size_t size_to_allocate = A_MEGABYTE;
some_memory = (char *)malloc(size_to_allocate);
sprintf(some_memory, "Hello World");
printf("%s\n", some_memory);
free(some_memory);

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

char *some_memory = "Hello World";

ณ จุดใดที่คุณต้องจัดสรรหน่วยความจำด้วยตัวคุณเองแทนที่จะประกาศ / เริ่มต้นค่าที่คุณต้องเก็บไว้


5
Re: ฉันละเว้นการตรวจสอบข้อผิดพลาดเพื่อความกะทัดรัด - น่าเสียดายที่โปรแกรมเมอร์จำนวนมากเกินไปที่ละเว้นการตรวจสอบข้อผิดพลาดเพราะพวกเขาไม่รู้ว่าmalloc()สามารถล้มเหลวได้!
Andrew

คำตอบ:


134
char *some_memory = "Hello World";

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

some_memory[0] = 'h';

กำลังขอความเดือดร้อน.

ในทางกลับกัน

some_memory = (char *)malloc(size_to_allocate);

กำลังจัดสรร char array (ตัวแปร) และ some_memory ชี้ไปยังหน่วยความจำที่จัดสรรนั้น ตอนนี้อาร์เรย์นี้ทั้งอ่านและเขียน ตอนนี้คุณสามารถทำได้:

some_memory[0] = 'h';

และเนื้อหาอาร์เรย์เปลี่ยนเป็น "hello World"


19
เพื่อชี้แจงเท่าที่ฉันชอบคำตอบนี้ (ฉันให้ +1 กับคุณ) คุณสามารถทำได้โดยไม่ต้อง malloc () โดยใช้อาร์เรย์อักขระ สิ่งที่ชอบ: char some_memory [] = "สวัสดี"; some_memory [0] = 'W'; ยังจะทำงาน
Randombits

19
ของคุณถูกต้อง คุณสามารถทำได้ เมื่อคุณใช้ malloc () หน่วยความจำจะถูกจัดสรรแบบไดนามิกในขณะรันดังนั้นคุณไม่จำเป็นต้องแก้ไขขนาดอาร์เรย์ในเวลาคอมไพล์นอกจากนี้คุณสามารถทำให้มันขยายหรือลดขนาดโดยใช้ realloc () สิ่งเหล่านี้ไม่สามารถทำได้เมื่อคุณทำ: char some_memory [] = "สวัสดี"; แม้ว่าคุณจะสามารถเปลี่ยนเนื้อหาของอาร์เรย์ได้ แต่ขนาดจะคงที่ ดังนั้นขึ้นอยู่กับความต้องการของคุณคุณใช้ตัวเลือกใดตัวเลือกหนึ่งในสามตัวเลือก: 1) ตัวชี้ไปยังถ่าน const 2) อาร์เรย์ที่จัดสรรแบบไดนามิก 3) ขนาดคงที่รวบรวมอาร์เรย์ที่จัดสรรเวลา
codaddict

เพื่อเน้นว่าเป็นแบบอ่านอย่างเดียวคุณควรเขียนconst char *s = "hi";สิ่งนี้ไม่จำเป็นต้องใช้ตามมาตรฐานจริงหรือ?
จนถึง Theis

1
@ แต่ไม่ใช่เพราะคุณประกาศตัวชี้เริ่มต้นไปยังที่อยู่ฐานของสตริงลิเทอรัล "hi" สามารถกำหนดใหม่ได้อย่างสมบูรณ์แบบตามกฎหมายเพื่อชี้ไปยังอักขระที่ไม่ใช่ const หากคุณต้องการตัวชี้ค่าคงที่เป็นสตริงแบบอ่านอย่างเดียวคุณต้องconst char const* s;
Rob11311

38

สำหรับตัวอย่างที่แน่นอน malloc มีประโยชน์น้อย

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

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


4
วงจรชีวิตของหน่วยความจำและคำถามที่เกี่ยวข้องว่าจะจัดสรรเวลาและอย่างไรเป็นปัญหาสำคัญกับไลบรารีและส่วนประกอบซอฟต์แวร์ทั่วไป โดยทั่วไปแล้วพวกเขาจะมีกฎที่มีการบันทึกไว้อย่างดี: "ถ้าคุณส่งตัวชี้ไปที่กิจวัตรนี้ของฉันคุณต้องมี malloc'd ฉันจะติดตามมันและปล่อยให้เป็นอิสระเมื่อฉันทำเสร็จแล้ว " แหล่งที่มาทั่วไปของจุดบกพร่องที่น่ารังเกียจคือการส่งตัวชี้ไปยังหน่วยความจำที่จัดสรรแบบคงที่ไปยังไลบรารีดังกล่าว เมื่อไลบรารีพยายามทำให้ว่าง () โปรแกรมจะหยุดทำงาน ฉันเพิ่งใช้เวลาส่วนใหญ่ในการแก้ไขข้อบกพร่องแบบที่คนอื่นเขียน
Bob Murphy

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

อายุการใช้งานของข้อมูลเป็นเหตุผลที่แท้จริงในการใช้ malloc Supose ประเภทข้อมูลนามธรรมจะแสดงโดยโมดูลโดยจะประกาศประเภทรายการและกิจวัตรในการเพิ่ม / ลบรายการจากรายการ ค่ารายการเหล่านั้นจำเป็นต้องคัดลอกไปยังหน่วยความจำที่จัดสรรแบบไดนามิก
Rob11311

@ บ็อบ: ข้อบกพร่องที่น่ารังเกียจเหล่านั้นทำให้การประชุมที่ผู้จัดสรรปลดปล่อยหน่วยความจำได้ดีกว่ามากหลังจากที่คุณสามารถรีไซเคิลได้ สมมติว่าคุณจัดสรรหน่วยความจำด้วย calloc เพื่อปรับปรุงตำแหน่งของการอ้างอิงซึ่งจะเปิดเผยลักษณะที่เสียของไลบรารีเหล่านั้นเนื่องจากคุณต้องโทรฟรีเพียงครั้งเดียวสำหรับทั้งบล็อก โชคดีที่ฉันไม่ต้องใช้ไลบรารีที่ระบุหน่วยความจำให้เป็น "malloc-ed" ไม่ใช่ประเพณี POSIX และมีแนวโน้มว่าจะถือว่าเป็นจุดบกพร่อง ถ้าพวกเขา "รู้" คุณต้องใช้ malloc ทำไมกิจวัตรของห้องสมุดไม่ทำเพื่อคุณ
Rob11311

17

malloc เป็นเครื่องมือที่ยอดเยี่ยมสำหรับการจัดสรรจัดสรรใหม่และเพิ่มหน่วยความจำที่รันไทม์เมื่อเทียบกับการประกาศแบบคงที่เช่นตัวอย่าง hello world ของคุณซึ่งประมวลผลตามเวลาคอมไพล์จึงไม่สามารถเปลี่ยนแปลงขนาดได้

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

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


7

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


เล็กน้อยปิดหัวข้อ แต่ ... mallocคุณจะต้องระมัดระวังไม่ให้สร้างหน่วยความจำรั่วเมื่อใช้ พิจารณารหัสนี้:

int do_something() {
    uint8_t* someMemory = (uint8_t*)malloc(1024);

    // Do some stuff

    if ( /* some error occured */ ) return -1;

    // Do some other stuff

    free(someMemory);
    return result;
}

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

แน่นอนว่านี่เป็นตัวอย่างที่ง่ายมากและง่ายมากที่จะเห็นข้อผิดพลาดที่นี่ แต่ลองนึกภาพโค้ดหลายร้อยบรรทัดที่เกลื่อนไปด้วยพอยน์เตอร์mallocsfree s และการจัดการข้อผิดพลาดทุกประเภท สิ่งต่างๆอาจยุ่งเหยิงเร็วมาก นี่เป็นหนึ่งในเหตุผลที่ฉันชอบ C ++ ที่ทันสมัยมากกว่า C ในกรณีที่เกี่ยวข้อง แต่นั่นเป็นหัวข้อสำคัญทั้งหมด

ดังนั้นเมื่อใดก็ตามที่คุณใช้mallocโปรดตรวจสอบให้แน่ใจเสมอว่าหน่วยความจำของคุณมีแนวโน้มที่จะfreed มากที่สุด


ตัวอย่างยอดเยี่ยม! ทางไป ^ _ ^
Musa Al-hassy

6
char *some_memory = "Hello World";
sprintf(some_memory, "Goodbye...");

ผิดกฎหมายตัวอักษรสตริงคือ constเป็นสิ่งผิดกฎหมายอักษรสตริงที่มี

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

char some_memory[] = "Hello World";

หากคุณต้องการออกจากที่ว่างสำหรับการจัดการเพิ่มเติมคุณสามารถระบุได้ว่าอาร์เรย์ควรมีขนาดใหญ่ขึ้น (โปรดอย่าใส่ 1MB ในสแต็ก)

#define LINE_LEN 80

char some_memory[LINE_LEN] = "Hello World";
strcpy(some_memory, "Goodbye, sad world...");
printf("%s\n", some_memory);

5

เหตุผลหนึ่งเมื่อจำเป็นต้องจัดสรรหน่วยความจำคือหากคุณต้องการแก้ไขในขณะรันไทม์ ในกรณีนั้นสามารถใช้ malloc หรือบัฟเฟอร์บนสแต็กได้ ตัวอย่างง่ายๆของการกำหนด "Hello World" ให้กับตัวชี้เป็นการกำหนดหน่วยความจำที่ "โดยทั่วไป" ไม่สามารถแก้ไขได้ในขณะรันไทม์

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.