ฉันใช้ malloc และ / หรือใหม่ในกรณีใดบ้าง


479

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

หากคุณเป็นผู้เชี่ยวชาญ C ++ โปรดแจ้งให้เราทราบกฎเกณฑ์หรือหลักการที่คุณปฏิบัติตามในเรื่องนี้


33
ฉันแค่ต้องการเพิ่มการเตือนความจำว่าคุณไม่สามารถผสมสองสไตล์ - นั่นคือคุณไม่สามารถใช้ใหม่เพื่อสร้างวัตถุแล้วโทรฟรี () บนมันหรือพยายามลบบล็อกที่จัดสรรโดย malloc () น่าจะเป็นที่ชัดเจนที่จะพูด แต่กระนั้น ...
nsayer

32
คำตอบที่ดีสิ่งที่ฉันต้องเพิ่ม (ที่ฉันไม่ได้เห็น) คือใหม่ / ลบเรียกตัวสร้าง / destructor สำหรับคุณ malloc / ฟรีไม่ได้ เพียงความแตกต่างมูลค่าการกล่าวขวัญ
Bill K

ด้วย C ++ ที่ทันสมัยฉันยังคงพยายามหาเหตุผลที่จะใช้อย่างใดอย่างหนึ่ง
Rahly

หรือใช้ไม่ได้และไปกับ std: shared_ptr <T> จากนั้นคุณไม่ต้องลบเลย
Vincent

คำตอบ:


387

ถ้าคุณถูกบังคับให้ใช้ C, คุณควรจะไม่เคยใช้ การใช้งานเสมอmallocnew

หากคุณต้องการข้อมูลจำนวนมากเพียงทำสิ่งต่อไปนี้:

char *pBuffer = new char[1024];

ระวังแม้ว่าสิ่งนี้จะไม่ถูกต้อง:

//This is incorrect - may delete only one element, may corrupt the heap, or worse...
delete pBuffer;

แต่คุณควรทำเช่นนี้เมื่อลบอาเรย์ของข้อมูล:

//This deletes all items in the array
delete[] pBuffer;

newคำหลักเป็นวิธีที่ C ++ ในการทำมันและมันจะช่วยให้มั่นใจได้ว่าประเภทของคุณจะได้ของคอนสตรัคที่เรียกว่า newคำหลักนอกจากนี้ยังมีพิมพ์ปลอดภัยในขณะที่mallocไม่ได้พิมพ์-ปลอดภัยตลอด

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

เป็นมูลค่าการกล่าวขวัญว่าคุณไม่สามารถผสมnew/ freeและ/mallocdelete

หมายเหตุ: คำตอบบางคำถามในคำถามนี้ไม่ถูกต้อง

int* p_scalar = new int(5);  // Does not create 5 elements, but initializes to 5
int* p_array  = new int[5];  // Creates 5 elements

2
เกี่ยวกับการโทรลบ foo เมื่อคุณควรโทรลบ [] foo คอมไพเลอร์บางตัวจะแก้ไขปัญหานี้โดยอัตโนมัติสำหรับคุณและไม่รั่วไหลและอื่น ๆ จะลบเฉพาะรายการแรกและการรั่วไหล ฉันมีบางส่วนของรหัสเหล่านี้และ valgrind จะหามาให้คุณ
KPexEA

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

8
@KPexEA: แม้ว่าคอมไพเลอร์บางคนอาจแก้ไขข้อผิดพลาดของคุณ แต่ก็ยังผิดที่จะทำให้มันเป็นที่แรก :) ใช้ delete [] ตามความเหมาะสม
korona

62
"หากคุณไม่จำเป็นต้องใช้ C คุณไม่ควรใช้ malloc ใช้ใหม่เสมอ" ทำไม? ชัยชนะที่นี่คืออะไร? สำหรับวัตถุที่เราต้องการสร้าง แต่สำหรับหน่วยความจำบล็อกคุณชัดเจนเอกสารสองวิธีในการทำผิดพลาดการเข้ารหัส (จับได้ง่ายขึ้น () vs [] ในใหม่และอาเรย์ที่ไม่ตรงกันจับได้ง่ายกว่าและ scaler ใหม่และลบ) แรงจูงใจในการใช้ใหม่ / ลบสำหรับบล็อกของหน่วยความจำดิบคืออะไร?
Ben Supnik

3
@DeadMG: หากมีการสร้างอาร์เรย์สำหรับใช้งานโดยฟังก์ชั่น API แบบอะซิงโครนัสจะnew[]ปลอดภัยกว่าstd::vectorหรือไม่? หากมีการใช้new[]วิธีเดียวที่ตัวชี้จะกลายเป็นไม่ถูกต้องนั้นจะใช้วิธีการอย่างชัดเจนdeleteในขณะที่หน่วยความจำที่จัดสรรสำหรับstd::vectorอาจไม่ถูกต้องเมื่อมีการปรับขนาดเวกเตอร์หรือออกจากขอบเขต (โปรดทราบว่าเมื่อใช้งานnew[]อย่างใดอย่างหนึ่งจะต้องอนุญาตให้เป็นไปได้ที่หนึ่งอาจไม่สามารถโทรdeleteหากวิธีการ async ยังคงอยู่ระหว่างการพิจารณาถ้ามันจำเป็นต้องละทิ้งการดำเนินการ async หนึ่งอาจต้องจัดให้มีการลบผ่านโทรกลับ) .
supercat

144

คำตอบสั้น ๆ คือ: อย่าใช้mallocกับ C ++ โดยไม่มีเหตุผลที่ดีในการทำเช่นนั้น mallocมีจำนวนข้อบกพร่องเมื่อใช้กับ C ++ ซึ่งnewถูกกำหนดให้เอาชนะ

แก้ไขข้อบกพร่องโดยใหม่สำหรับรหัส C ++

  1. mallocไม่ใช่ประเภทที่ปลอดภัยในทางที่มีความหมายใด ๆ ใน C ++ void*คุณจะต้องโยนกลับมาจาก สิ่งนี้อาจทำให้เกิดปัญหามากมาย:

    #include <stdlib.h>
    
    struct foo {
      double d[5];
    }; 
    
    int main() {
      foo *f1 = malloc(1); // error, no cast
      foo *f2 = static_cast<foo*>(malloc(sizeof(foo)));
      foo *f3 = static_cast<foo*>(malloc(1)); // No error, bad
    }
  2. มันแย่กว่านั้น หากประเภทที่มีปัญหาคือPOD (ข้อมูลเก่าธรรมดา)คุณสามารถใช้mallocการจัดสรรหน่วยความจำแบบกึ่งมีเหตุผลได้เช่นเดียวกับf2ในตัวอย่างแรก

    ไม่ชัดเจนแม้ว่าจะเป็นประเภท POD ก็ตาม ความจริงที่ว่าเป็นไปได้สำหรับประเภทที่กำหนดให้เปลี่ยนจาก POD เป็น non-POD โดยไม่มีข้อผิดพลาดของคอมไพเลอร์และอาจเป็นปัญหาที่ยากมากในการดีบักเป็นปัจจัยสำคัญ ตัวอย่างเช่นหากมีใครบางคน (อาจเป็นโปรแกรมเมอร์อีกคนหนึ่งในระหว่างการบำรุงรักษามากขึ้นในภายหลังเพื่อทำการเปลี่ยนแปลงที่ทำให้fooไม่มี POD อีกต่อไปแล้วจะไม่มีข้อผิดพลาดที่ชัดเจนปรากฏขึ้นในเวลารวบรวมตามที่คุณต้องการเช่น:

    struct foo {
      double d[5];
      virtual ~foo() { }
    };

    จะทำให้mallocการf2ก็จะกลายเป็นไม่ดีโดยไม่ต้องวินิจฉัยใด ๆ ที่ชัดเจน ตัวอย่างที่นี่เป็นเรื่องเล็กน้อย แต่เป็นไปได้ที่จะนำเสนอผลิตภัณฑ์ที่ไม่ใช่ PODness ออกไปโดยไม่ตั้งใจ (เช่นในชั้นฐานโดยเพิ่มสมาชิกที่ไม่ใช่ POD) หากคุณมี C ++ 11 / boost คุณสามารถใช้is_podเพื่อตรวจสอบว่าสมมติฐานนี้ถูกต้องและสร้างข้อผิดพลาดหากไม่ใช่:

    #include <type_traits>
    #include <stdlib.h>
    
    foo *safe_foo_malloc() {
      static_assert(std::is_pod<foo>::value, "foo must be POD");
      return static_cast<foo*>(malloc(sizeof(foo)));
    }

    แม้ว่าการเพิ่มจะไม่สามารถระบุได้ว่าประเภทใดเป็น POD ที่ไม่มี C ++ 11 หรือส่วนขยายคอมไพเลอร์อื่น ๆ

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

    #include <type_traits>
    #include <stdlib.h>
    
    void my_malloc_failed_handler();
    
    foo *safe_foo_malloc() {
      static_assert(std::is_pod<foo>::value, "foo must be POD");
      foo *mem = static_cast<foo*>(malloc(sizeof(foo)));
      if (!mem) {
         my_malloc_failed_handler();
         // or throw ...
      }
      return mem;
    }
  4. พื้นฐานmallocคือคุณสมบัติ C และnewเป็นคุณสมบัติ C ++ ผลที่ตามมาmallocไม่ได้เล่นได้ดีกับตัวสร้างมันเพียงดูที่การจัดสรรจำนวนไบต์ เราสามารถขยายsafe_foo_mallocเพิ่มเติมเพื่อใช้ตำแหน่งnew:

    #include <stdlib.h>
    #include <new>
    
    void my_malloc_failed_handler();
    
    foo *safe_foo_malloc() {
      void *mem = malloc(sizeof(foo));
      if (!mem) {
         my_malloc_failed_handler();
         // or throw ...
      }
      return new (mem)foo();
    }
  5. เราsafe_foo_mallocฟังก์ชั่นไม่ได้ทั่วไปมาก - นึกคิดเราต้องการบางสิ่งบางอย่างที่สามารถจัดการกับชนิดใด ๆ fooที่ไม่เพียง เราสามารถทำสิ่งนี้ได้ด้วยเทมเพลตและเทมเพลตแบบแปรผันสำหรับตัวสร้างที่ไม่ใช่ค่าเริ่มต้น:

    #include <functional>
    #include <new>
    #include <stdlib.h>
    
    void my_malloc_failed_handler();
    
    template <typename T>
    struct alloc {
      template <typename ...Args>
      static T *safe_malloc(Args&&... args) {
        void *mem = malloc(sizeof(T));
        if (!mem) {
           my_malloc_failed_handler();
           // or throw ...
        }
        return new (mem)T(std::forward(args)...);
      }
    };

    ถึงแม้ว่าในการแก้ไขปัญหาทั้งหมดที่เราระบุจนถึงขณะนี้เราได้คิดค้นตัวnewดำเนินการเริ่มต้นใหม่ หากคุณกำลังจะใช้งานmallocและการจัดวางnewคุณอาจnewเริ่มใช้ด้วย!


27
มันทำซีพลัสพลัสที่แย่มากstructและclassหมายความว่าเหมือนกัน ฉันสงสัยว่าจะมีปัญหาใด ๆ กับการstructสำรองไว้สำหรับ POD หรือไม่และอาจมีทุกclassประเภทให้สันนิษฐานว่าไม่ใช่ POD ประเภทใดก็ตามที่กำหนดโดยรหัสซึ่งมีการคาดการณ์การประดิษฐ์ของ C ++ จะต้องเป็น POD ดังนั้นฉันจึงไม่คิดว่าความเข้ากันได้แบบย้อนหลังจะเป็นปัญหาที่นั่น มีข้อได้เปรียบในการมีประเภทที่ไม่ใช่ PODs-ประกาศเป็นstructมากกว่าclass?
supercat

1
@supercat สายไปนิด แต่มันจะเปิดออกทำให้structและclassทำเกือบสิ่งเดียวกันคือการตัดสินใจที่ออกแบบที่ยอดเยี่ยมที่ตอนนี้ช่วยให้คุณสมบัติอย่างที่เรียกว่า"metaclasses" (จากสมุนไพร)
Rakete1111

@ Rakete1111: จากภาพรวมในครั้งแรกข้อเสนอดังกล่าวดูเหมือนว่าจะประมวลผลภาษาที่ใช้คำหลักที่มีคำนำหน้าด้วยเงินดอลลาร์เช่น$classกัน ฉันไม่แน่ใจว่าเกี่ยวข้องกับอะไรclassและstructเป็นคำพ้องความหมายอย่างไร
supercat

@supercat ระบบประเภทจะได้รับการแยกไปสองทางเพิ่มเติม ด้วยการมีclassและความstructหมายเดียวกันอย่างมีประสิทธิภาพคุณสามารถทำการเปลี่ยนแปลงโดยพลการกับพวกมัน ( $class) โดยไม่ต้องกังวลกับการทำclassa structและในทางกลับกัน
Rakete1111

@ Rakete1111: หากการดำเนินการและการแปลงบางประเภทมีความปลอดภัยในบางประเภท แต่ไม่ใช่ประเภทอื่นการระบุประเภทนั้นโดยตรงและการคอมไพเลอร์ปฏิเสธการดำเนินการและการแปลงที่ไม่ปลอดภัยดูเหมือนจะดีกว่าการเปลี่ยน metaclass ที่ใช้ใน วิธีที่เหมาะสำหรับ PODS เท่านั้นรับการเปลี่ยนเป็น PODS ที่ไม่ใช่แบบเงียบ ๆ
supercat

53

จากC ++ FQA Lite :

[16.4] ทำไมฉันจึงควรใช้ใหม่แทน malloc เก่าที่น่าเชื่อถือ ()

คำถามที่พบบ่อย: ใหม่ / ลบเรียกตัวสร้าง / destructor; ใหม่เป็นประเภทที่ปลอดภัย malloc ไม่ได้; ใหม่สามารถถูกแทนที่โดยชั้นเรียน

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

โอ้และการใช้ malloc เก่าที่น่าเชื่อถือทำให้สามารถใช้ realloc ที่น่าเชื่อถือและเท่ากัน น่าเสียดายที่เราไม่มีผู้ให้บริการรายใหม่หรือสิ่งใด

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

ขอโทษฉันไม่สามารถต้านทานได้ :)


7
นั่นคือจลาจล ! ขอบคุณ
dmckee --- ผู้ดูแลอดีตลูกแมว

8
ฉันไม่สามารถแสดงความคิดเห็นนี้อย่างจริงจังเพราะเห็นได้ชัดว่าผู้เขียนเอนเอียงกับ C ++ C ++ เป็นภาษาที่ใช้ในการสร้างซอฟต์แวร์ที่มุ่งเน้นประสิทธิภาพและตัวรวบรวมขยะอาจเป็นอันตรายต่อวัตถุประสงค์ของมันเท่านั้น ฉันไม่เห็นด้วยกับคำตอบทั้งหมดของคุณ!
Miguel

1
@Miguel คุณพลาดเรื่องตลก
Dan Bechard

50

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

void *p = operator new(size);
   ...
operator delete(p);

3
ที่น่าสนใจฉันมักจะจัดสรรอาร์เรย์ของถ่านที่ไม่ได้ลงนามเมื่อฉันต้องการบัฟเฟอร์ข้อมูลดิบเช่นนี้
Greg Rogers

ระวัง semmantics ควรเป็นเช่นนี้: p_var = new type (initializer); ไม่ขนาด
Brian R. Bondy

11
ไม่ใช่ถ้าคุณเรียกโอเปอเรเตอร์ใหม่โดยตรงมันจะใช้จำนวนไบต์ในการจัดสรรเป็นพารามิเตอร์
Ferruccio

1
ไม่แน่ใจฉันไม่เคยได้ยินเรื่องนี้มาก่อน
Brian R. Bondy

9
ตรงข้ามคือoperator new operator deleteมันไม่ได้เป็นการกระทำที่กำหนดไว้อย่างดีต่อการเรียกร้องในการแสดงออกที่มีประเภทdelete void*
CB Bailey

33

ใช้mallocและเพียงการจัดสรรหน่วยความจำที่กำลังจะถูกจัดการโดยห้องสมุดคเป็นศูนย์กลางและ API ใช้และ(และตัวแปร) สำหรับทุกสิ่งที่คุณควบคุมfree newdelete[]


10
นอกจากนี้โปรดสังเกตว่าไลบรารี C ที่เขียนได้ดีจะซ่อน malloc และฟรีภายในนี่คือวิธีที่โปรแกรมเมอร์ C ควรทำงาน
Dacav

@dmckee คุณมีตัวอย่างของ C ++ ที่ใช้ไลบรารี c-centric โดย malloc และฟรีหรือไม่
milesma

1
@Dacav: หากฟังก์ชั่น C จะยอมรับตัวชี้ไปยังวัตถุที่จะต้องใช้ต่อไปหลังจากที่ฟังก์ชั่นกลับมาและผู้โทรจะไม่มีทางรู้ว่าเมื่อวัตถุยังคงต้องการมันจะเป็นเหตุผลที่สมบูรณ์แบบสำหรับฟังก์ชั่น mallocเพื่อระบุว่าตัวชี้ต้องได้รับการสร้างขึ้นด้วย ในทำนองเดียวกันถ้าฟังก์ชั่นที่strdupต้องการสร้างวัตถุและส่งกลับไปยังผู้เรียกมันก็มีเหตุผลที่สมบูรณ์แบบที่จะระบุว่าผู้โทรต้องเรียกfreeบนวัตถุเมื่อมันไม่จำเป็นอีกต่อไป ฟังก์ชั่นดังกล่าวจะหลีกเลี่ยงการเปิดเผยการใช้งาน malloc / ฟรีกับผู้โทรได้อย่างไร?
supercat

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

@supercat ตัวอย่างหนึ่งของแพ็คเกจทุกวันซึ่งฉันแน่ใจว่าทุกคนใช้แล้วคือ libgmp หากคุณเคยใช้การเข้ารหัสโอเพนซอร์ซหรือซอฟต์แวร์ตามการเข้ารหัสดังกล่าว (ซึ่งมีแนวโน้มมาก) คุณอาจใช้ห้องสมุดเลขคณิตความแม่นยำโดยพลการซึ่งต้องการขยายและลดขนาดข้อมูลภายในของตัวเอง สิ่งนี้ทำได้โดยใช้ฟังก์ชัน initialisation ... แล้วคุณต้องสงสัยว่าคุณใช้รหัส C ซึ่งเป็น libgmp ใน C ++ ได้อย่างไรโดยไม่ต้องคอมไพล์ใหม่ใน C ++ ตอนนี้เมื่อนึกถึงสิ่งนี้ (ผู้ทำลิงก์) ลองคิดดู ... ทำไมคนที่มีเหตุผลจะใส่mallocC ++ ลงไป?
ออทิสติก

31

ใหม่ vs malloc ()

1) newคือผู้ประกอบการในขณะที่malloc()เป็นฟังก์ชั่น

2) newเรียกconstructorsในขณะที่malloc()ไม่

3) newผลตอบแทนชนิดข้อมูลที่แน่นอนในขณะที่malloc()ผลตอบแทนเป็นโมฆะ *

4) newไม่ส่งคืนNULL (จะเกิดความล้มเหลว) ในขณะที่malloc()ส่งคืน NULL

5) การจัดสรรหน่วยความจำที่ไม่สามารถจัดการได้newในขณะที่malloc()สามารถ


6
สวัสดีสำหรับจุดที่ 4) คำสั่งใหม่สามารถสั่งให้คืนค่า NULL เมื่อเกิดความล้มเหลว char* ptr = new (std::nothrow) char [323232];
ซิงห์

1
6) สร้างใหม่จากการสร้างข้อโต้แย้งในขณะที่ malloc ใช้ขนาด
Evan Moran

นอกจากนี้ยังมีnewฟังก์ชั่น
Ma Ming

หากคุณมีความโน้มเอียงใน C ที่จะจัดสรรใหม่ฉันหวังว่าคุณจะใช้reallocมากกว่าmallocและเริ่มต้นด้วยตัวแปรตัวชี้ของคุณที่เริ่มต้นNULLด้วย หากคุณต้องการหน่วยความจำที่ปรับขนาดได้ใน C ++ ในทางกลับกันฉันจะแนะนำstd::vectorว่าไม่ใช่realloc... นั่นหรือไฟล์
ออทิสติก

19

เพื่อที่จะตอบคำถามของคุณที่คุณควรทราบความแตกต่างระหว่างmallocnewและ ความแตกต่างนั้นง่าย:

malloc จัดสรรหน่วยความจำในขณะที่new จัดสรรหน่วยความจำและเรียกตัวสร้างของวัตถุที่คุณกำลังจัดสรรหน่วยความจำ

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

ความแตกต่างระหว่างfreeและdeleteค่อนข้างเหมือนกัน ความแตกต่างคือdeleteจะเรียกตัวทำลายของวัตถุของคุณนอกเหนือจากการเพิ่มหน่วยความจำ


13

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

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

newจัดสรรหน่วยความจำและสร้างวัตถุบนตำแหน่งหน่วยความจำนั้น สำหรับประเภทที่ไม่ใช่ POD นี่หมายถึงการเรียกตัวสร้าง

หากคุณทำสิ่งนี้:

non_pod_type* p = (non_pod_type*) malloc(sizeof *p);

ตัวชี้ที่คุณได้รับไม่สามารถถูกยกเลิกการลงทะเบียนได้เนื่องจากไม่ได้ชี้ไปที่วัตถุ คุณจะต้องเรียกใช้ตัวสร้างมันก่อนที่คุณจะสามารถใช้มันได้newได้

ในทางกลับกันถ้าคุณทำ:

non_pod_type* p = new non_pod_type();

คุณได้รับตัวชี้ที่ถูกต้องเสมอเพราะ newสร้างวัตถุ

แม้สำหรับ POD ประเภทนั้นมีความแตกต่างที่สำคัญระหว่างสอง:

pod_type* p = (pod_type*) malloc(sizeof *p);
std::cout << p->foo;

โค้ดนี้จะพิมพ์ค่าที่ไม่ระบุเนื่องจากวัตถุ POD ที่สร้างโดยmallocไม่ได้เริ่มต้น

ด้วยnewคุณสามารถระบุนวกรรมิกที่จะเรียกใช้และทำให้ได้ค่าที่กำหนดไว้อย่างดี

pod_type* p = new pod_type();
std::cout << p->foo; // prints 0

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

ความแตกต่างก็คือพฤติกรรมเมื่อล้มเหลว เมื่อไม่สามารถจัดสรรหน่วยความจำได้mallocจะส่งคืนพอยน์เตอร์พอยน์เตอร์ในขณะที่newส่งข้อยกเว้น

ตัวเก่าต้องการให้คุณทดสอบตัวชี้ทุกตัวที่ส่งคืนมาก่อนที่จะใช้ตัวชี้ในขณะที่ตัวชี้ภายหลังจะสร้างตัวชี้ที่ถูกต้องอยู่เสมอ

ด้วยเหตุผลเหล่านี้ใน C ++ รหัสที่คุณควรใช้และไม่new mallocแต่ถึงอย่างนั้นคุณก็ไม่ควรใช้new"in the open" เพราะมันจะได้รับทรัพยากรที่คุณจำเป็นต้องใช้ในภายหลัง เมื่อคุณใช้newคุณควรส่งผลทันทีไปยังคลาสการจัดการทรัพยากร:

std::unique_ptr<T> p = std::unique_ptr<T>(new T()); // this won't leak

7

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

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

 std::vector<int> *createVector(); // Bad
 std::vector<int> createVector();  // Good

 auto v = new std::vector<int>(); // Bad
 auto result = calculate(/*optional output = */ v);
 auto v = std::vector<int>(); // Good
 auto result = calculate(/*optional output = */ &v);

ตั้งแต่ C ++ 11 เป็นต้นไปเรามีstd::unique_ptrการจัดการกับหน่วยความจำที่จัดสรรซึ่งมีความเป็นเจ้าของหน่วยความจำที่จัดสรร std::shared_ptrถูกสร้างขึ้นเมื่อคุณต้องแบ่งปันความเป็นเจ้าของ (คุณต้องใช้โปรแกรมนี้น้อยกว่าที่คาดไว้ในโปรแกรมที่ดี)

การสร้างตัวอย่างนั้นง่ายมาก:

auto instance = std::make_unique<Class>(/*args*/); // C++14
auto instance = std::make_unique<Class>(new Class(/*args*/)); // C++11
auto instance = std::make_unique<Class[]>(42); // C++14
auto instance = std::make_unique<Class[]>(new Class[](42)); // C++11

C ++ 17 ยังเพิ่มstd::optionalสิ่งที่ป้องกันไม่ให้คุณต้องการการจัดสรรหน่วยความจำ

auto optInstance = std::optional<Class>{};
if (condition)
    optInstance = Class{};

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

 auto vector = std::vector<std::unique_ptr<Interface>>{};
 auto instance = std::make_unique<Class>();
 vector.push_back(std::move(instance)); // std::move -> transfer (most of the time)

ดังนั้นเมื่อใดที่คุณยังคงต้องnew? แทบไม่เคยมาจาก C ++ 11 ส่วนใหญ่คุณใช้std::make_uniqueจนกว่าจะถึงจุดที่คุณตี API ที่โอนความเป็นเจ้าของผ่านตัวชี้ดิบ

 auto instance = std::make_unique<Class>();
 legacyFunction(instance.release()); // Ownership being transferred

 auto instance = std::unique_ptr<Class>{legacyFunction()}; // Ownership being captured in unique_ptr

ใน C ++ 98/03 คุณต้องจัดการหน่วยความจำด้วยตนเอง หากคุณอยู่ในกรณีนี้ให้ลองอัปเกรดเป็นรุ่นที่ใหม่กว่าของมาตรฐาน หากคุณติดขัด:

 auto instance = new Class(); // Allocate memory
 delete instance;             // Deallocate
 auto instances = new Class[42](); // Allocate memory
 delete[] instances;               // Deallocate

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

ดังนั้นเมื่อไหร่ที่เราต้องการ malloc ใน C ++ เหตุผลที่ถูกต้องเพียงอย่างเดียวคือการจัดสรรหน่วยความจำและเตรียมใช้งานในภายหลังผ่านตำแหน่งใหม่

 auto instanceBlob = std::malloc(sizeof(Class)); // Allocate memory
 auto instance = new(instanceBlob)Class{}; // Initialize via constructor
 instance.~Class(); // Destroy via destructor
 std::free(instanceBlob); // Deallocate the memory

แม้ว่าข้างต้นจะถูกต้อง แต่ก็สามารถทำได้ผ่านตัวดำเนินการใหม่เช่นกัน std::vectorเป็นตัวอย่างที่ดีสำหรับสิ่งนี้

สุดท้ายเรายังมีช้างอยู่ในห้อง: C. หากคุณต้องทำงานกับ C-library ซึ่งหน่วยความจำได้รับการจัดสรรในรหัส C ++ และปลดปล่อยในรหัส C (หรือวิธีอื่น ๆ ) คุณจำเป็นต้องใช้ malloc / free

หากคุณอยู่ในกรณีนี้ลืมเกี่ยวกับฟังก์ชั่นเสมือนฟังก์ชั่นสมาชิกคลาส ... อนุญาตให้ใช้กับ PODs ที่มี POD เท่านั้น

ข้อยกเว้นบางประการสำหรับกฎ:

  • คุณกำลังเขียนไลบรารีมาตรฐานที่มีโครงสร้างข้อมูลขั้นสูงที่ malloc เหมาะสม
  • คุณต้องจัดสรรหน่วยความจำจำนวนมาก (ในหน่วยความจำของไฟล์ 10GB?)
  • คุณมีเครื่องมือที่ป้องกันไม่ให้คุณใช้โครงสร้างบางอย่าง
  • คุณต้องจัดเก็บประเภทที่ไม่สมบูรณ์

6

มีบางสิ่งที่newทำmallocไม่ได้:

  1. new สร้างวัตถุโดยการเรียกตัวสร้างของวัตถุนั้น
  2. new ไม่จำเป็นต้องพิมพ์หน่วยความจำที่จัดสรร
  3. ไม่จำเป็นต้องจัดสรรจำนวนหน่วยความจำ แต่ต้องการจำนวนวัตถุที่จะสร้าง

ดังนั้นถ้าคุณใช้mallocคุณต้องทำสิ่งต่าง ๆ อย่างชัดเจนซึ่งไม่สามารถนำไปใช้ได้จริงเสมอไป นอกจากนี้newสามารถโอเวอร์โหลดได้ แต่mallocไม่สามารถ


5

หากคุณทำงานกับข้อมูลที่ไม่ต้องการการก่อสร้าง / การทำลายและต้องการการจัดสรรใหม่ (เช่น ints จำนวนมาก) ฉันเชื่อว่า malloc / free เป็นตัวเลือกที่ดีเพราะให้คุณจัดสรรใหม่ซึ่งเร็วกว่า memcpy แบบใหม่ - ลบ (มันอยู่ในกล่อง Linux ของฉัน แต่ฉันเดาว่านี่อาจขึ้นอยู่กับแพลตฟอร์ม) หากคุณทำงานกับวัตถุ C ++ ที่ไม่ใช่ POD และต้องการการก่อสร้าง / ทำลายคุณต้องใช้ตัวดำเนินการใหม่และตัวลบ

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

นอกจากว่าคุณต้องการมันแล้วคุณควรติดใหม่ / ลบใน C ++


3

หากคุณมีรหัส C ที่คุณต้องการย้ายพอร์ตไปยัง C ++ คุณอาจปล่อยให้ malloc () โทรออก สำหรับรหัส C ++ ใหม่ใด ๆ ฉันขอแนะนำให้ใช้รหัสใหม่แทน


3

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


2

new จะเริ่มต้นค่าเริ่มต้นของ struct และเชื่อมโยงการอ้างอิงในตัวมันเองอย่างถูกต้อง

เช่น

struct test_s {
    int some_strange_name = 1;
    int &easy = some_strange_name;
}

ดังนั้นnew struct test_sจะส่งกลับโครงสร้างเริ่มต้นด้วยการอ้างอิงการทำงานในขณะที่รุ่น malloc'ed ไม่มีค่าเริ่มต้นและการอ้างอิงฝึกงานไม่ได้เริ่มต้น


1

จากมุมมองด้านล่างใหม่จะเริ่มต้นหน่วยความจำทั้งหมดก่อนที่จะให้หน่วยความจำในขณะที่ malloc จะเก็บเนื้อหาต้นฉบับของหน่วยความจำ


4
ใหม่ไม่ได้อยู่ในหน่วยความจำเริ่มต้นทั่วไปแม้ว่าจะมีวิธีที่จะทำให้เกิดขึ้นได้: ดูstackoverflow.com/questions/2204176/…สำหรับการสนทนาหนึ่งเรื่อง
wjl

0

ในสถานการณ์ต่อไปนี้เราไม่สามารถใช้สิ่งใหม่ได้เนื่องจากมันเรียกตัวสร้าง

class  B  {
private:
    B *ptr;
    int x;
public:
    B(int n)  {
        cout<<"B: ctr"<<endl;
        //ptr = new B;  //keep calling ctr, result is segmentation fault
        ptr = (B *)malloc(sizeof(B));
        x = n;
        ptr->x = n + 10;
    }
    ~B()  {
        //delete ptr;
        free(ptr);
        cout<<"B: dtr"<<endl;
    }
};

0

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

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


0

กรณีที่หายากที่จะต้องพิจารณาการใช้ malloc / free แทน new / delete คือเมื่อคุณจัดสรรและจัดสรรใหม่ (ประเภท pod แบบง่าย ๆ ไม่ใช่วัตถุ) โดยใช้ realloc เนื่องจากไม่มีฟังก์ชั่นที่คล้ายกันในการ realloc ใน C ++ (แม้ว่าจะสามารถทำได้โดยใช้ วิธี C ++ เพิ่มเติม)


-4

malloc () ถูกใช้เพื่อกำหนดหน่วยความจำแบบไดนามิกใน C ในขณะที่งานเดียวกันทำโดย new () ใน c ++ ดังนั้นคุณไม่สามารถผสมอนุสัญญาการเข้ารหัสของ 2 ภาษาได้ มันจะดีถ้าคุณถามความแตกต่างระหว่าง calloc และ malloc ()


2
คุณสามารถใช้ (แต่ไม่ควรทำเกือบทุกครั้ง) mallocใน C ++
ระหว่าง

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