Trong trường hợp nào tôi sử dụng malloc và / hoặc mới?


479

Tôi thấy trong C ++ có nhiều cách để phân bổ và dữ liệu miễn phí và tôi hiểu rằng khi bạn gọi mallocbạn nên gọi freevà khi bạn sử dụng newtoán tử, bạn nên ghép nối với nhau deletevà đó là một sai lầm khi kết hợp cả hai (ví dụ: Gọi free()vào thứ gì đó đã được tạo với newnhà điều hành), nhưng tôi không rõ khi nào tôi nên sử dụng malloc/ freevà khi nào tôi nên sử dụng new/ deletetrong các chương trình trong thế giới thực của mình.

Nếu bạn là chuyên gia về C ++, vui lòng cho tôi biết bất kỳ quy tắc nào hoặc quy ước bạn tuân theo về vấn đề này.


33
Tôi chỉ muốn thêm một lời nhắc rằng bạn không thể trộn lẫn hai kiểu - đó là, bạn không thể sử dụng mới để tạo một đối tượng và sau đó gọi free () trên đó, cũng không cố xóa một khối được cấp bởi malloc (). Có lẽ rõ ràng để nói điều đó, nhưng dù sao ...
nsayer

32
Câu trả lời hay, tất cả những gì tôi phải thêm (mà tôi chưa thấy) là mới / xóa gọi hàm tạo / hàm hủy cho bạn, malloc / free thì không. Chỉ là một sự khác biệt đáng nói.
Bill K

Với C ++ hiện đại, tôi vẫn đang cố gắng tìm một lý do để sử dụng.
Rahly

Hoặc không sử dụng và đi với std: shared_ptr <T>. Sau đó, bạn không cần phải xóa tất cả.
Vincent

Câu trả lời:


387

Trừ khi bạn bị buộc phải sử dụng C, bạn không bao giờ nên sử dụngmalloc . Luôn luôn sử dụng new.

Nếu bạn cần một khối dữ liệu lớn, chỉ cần làm một cái gì đó như:

char *pBuffer = new char[1024];

Hãy cẩn thận mặc dù điều này không đúng:

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

Thay vào đó, bạn nên làm điều này khi xóa một mảng dữ liệu:

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

Các newtừ khóa là C ++ cách để làm việc đó, và nó sẽ đảm bảo rằng loại của bạn sẽ có nó constructor được gọi . Các newtừ khóa cũng nhiều kiểu an trong khi mallockhông phải là loại an toàn chút nào.

Cách duy nhất tôi có thể nghĩ rằng sẽ có ích khi sử dụng malloclà nếu bạn cần thay đổi kích thước bộ đệm dữ liệu của mình. Các newtừ khóa không có một cách tương tự như realloc. Các reallocchức năng có thể có thể để mở rộng kích thước của một đoạn bộ nhớ cho bạn hiệu quả hơn.

Điều đáng nói là bạn không thể trộn new/ freemalloc/ delete.

Lưu ý: Một số câu trả lời trong câu hỏi này không hợp lệ.

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
Liên quan đến việc gọi xóa foo khi bạn nên gọi xóa [] foo, một số trình biên dịch sẽ tự động sửa lỗi này cho bạn và không bị rò rỉ và những người khác sẽ chỉ xóa mục nhập và rò rỉ đầu tiên. Tôi đã có một vài trong số này trong một số mã và valgrind sẽ tìm thấy chúng cho bạn.
KPexEA

30
Nếu bạn không sử dụng xóa chính xác , kết quả là không xác định . Không đúng. Thực tế là đôi khi nó có thể trở thành một phần của sự việc hoặc công việc chỉ là may mắn mù quáng.
Michael Burr

8
@KPexEA: Ngay cả khi một số trình biên dịch có thể sửa lỗi của bạn, thì vẫn là sai khi đặt chúng ở vị trí đầu tiên :) Luôn sử dụng xóa [] khi thích hợp.
korona

62
"Trừ khi bạn bị buộc phải sử dụng C, bạn không bao giờ nên sử dụng malloc. Luôn luôn sử dụng mới." Tại sao? Chiến thắng ở đây là gì? Đối với các đối tượng chúng ta cần xây dựng, nhưng đối với các khối bộ nhớ, bạn ghi lại rõ ràng hai cách để mắc lỗi mã hóa (càng dễ bị bắt () so với [] trong mảng mới và mảng dễ bị bắt hơn so với quy mô mới và xóa). Động lực để sử dụng mới / xóa cho các khối bộ nhớ thô là gì?
Ben Supnik

3
@DeadMG: Nếu một người đang tạo một mảng để sử dụng bởi hàm API không đồng bộ, sẽ new[]an toàn hơn nhiều so với std::vector? Nếu một người sử dụng new[], cách duy nhất con trỏ trở nên không hợp lệ là thông qua rõ ràng delete, trong khi bộ nhớ được phân bổ cho một std::vectorcó thể bị vô hiệu hóa khi vectơ được thay đổi kích thước hoặc để lại phạm vi. (Lưu ý rằng khi sử dụng, new[]người ta sẽ phải cho phép khả năng người ta không thể gọi deletenếu phương thức async vẫn đang chờ xử lý; nếu có thể cần phải từ bỏ thao tác async, người ta có thể phải sắp xếp để xóa qua gọi lại) .
supercat

144

Câu trả lời ngắn gọn là: không sử dụng malloccho C ++ mà không có lý do thực sự tốt để làm như vậy. malloccó một số thiếu sót khi được sử dụng với C ++, newđược xác định để khắc phục.

Các thiếu sót được sửa bởi mã mới C ++

  1. mallockhông phải là loại an toàn trong bất kỳ cách có ý nghĩa. Trong C ++, bạn được yêu cầu bỏ trả về void*. Điều này có khả năng giới thiệu rất nhiều vấn đề:

    #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. Nó còn tệ hơn thế nữa. Nếu loại trong câu hỏi là POD (dữ liệu cũ đơn giản) thì bạn có thể sử dụng một cách hợp lý mallocđể phân bổ bộ nhớ cho nó, như f2trong ví dụ đầu tiên.

    Nó không quá rõ ràng mặc dù nếu một loại là POD. Thực tế là một loại nhất định có thể thay đổi từ POD sang không phải POD mà không có lỗi trình biên dịch và có khả năng rất khó để gỡ lỗi là một yếu tố quan trọng. Ví dụ: nếu ai đó (có thể là một lập trình viên khác, trong quá trình bảo trì, sau này sẽ thực hiện một thay đổi gây ra fookhông còn là POD thì sẽ không có lỗi rõ ràng nào xuất hiện vào thời gian biên dịch như bạn mong muốn, ví dụ:

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

    sẽ làm cho malloccác f2xấu cũng trở thành, mà không cần bất kỳ chẩn đoán rõ ràng. Ví dụ ở đây là tầm thường, nhưng có thể vô tình giới thiệu phi PODness ở rất xa (ví dụ: trong một lớp cơ sở, bằng cách thêm một thành viên không phải POD). Nếu bạn có C ++ 11 / boost, bạn có thể sử dụng is_podđể kiểm tra xem giả định này có đúng không và gây ra lỗi nếu không:

    #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)));
    }
    

    Mặc dù boost không thể xác định nếu một loại là POD mà không có C ++ 11 hoặc một số phần mở rộng trình biên dịch khác.

  3. malloctrả về NULLnếu phân bổ thất bại. newsẽ ném std::bad_alloc. Hành vi của sau này sử dụng một NULLcon trỏ là không xác định. Một ngoại lệ có ngữ nghĩa rõ ràng khi nó được ném và nó được ném từ nguồn lỗi. Kết thúc mallocvới một bài kiểm tra thích hợp ở mỗi cuộc gọi có vẻ tẻ nhạt và dễ bị lỗi. (Bạn chỉ phải quên một lần để hoàn tác tất cả công việc tốt đó). Một ngoại lệ có thể được phép truyền tới một mức độ mà người gọi có thể xử lý nó một cách hợp lý, trong đó NULLkhó hơn nhiều để có thể quay trở lại một cách có ý nghĩa. Chúng tôi có thể mở rộng safe_foo_mallocchức năng của mình để đưa ra một ngoại lệ hoặc thoát khỏi chương trình hoặc gọi một số trình xử lý:

    #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. Về cơ bản malloclà một tính năng C và newlà một tính năng C ++. Kết quả là mallockhông chơi tốt với các hàm tạo, nó chỉ nhìn vào việc phân bổ một đoạn byte. Chúng tôi có thể mở rộng safe_foo_mallochơn nữa để sử dụng vị trí 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_mallocChức năng của chúng tôi không chung chung - lý tưởng là chúng tôi muốn một cái gì đó có thể xử lý bất kỳ loại nào, không chỉ foo. Chúng ta có thể đạt được điều này với các mẫu và mẫu matrixdic cho các hàm tạo không mặc định:

    #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)...);
      }
    };
    

    Bây giờ mặc dù trong việc khắc phục tất cả các vấn đề chúng tôi đã xác định cho đến nay, chúng tôi thực tế đã phát minh lại newtoán tử mặc định . Nếu bạn sẽ sử dụng mallocvà sắp xếp newthì bạn cũng có thể sử dụng newđể bắt đầu!


27
C ++ quá tệ được tạo ra structclassvề cơ bản là giống nhau; Tôi tự hỏi liệu sẽ có bất kỳ vấn đề nào với việc structđược dành riêng cho POD và có thể có tất cả các classloại được coi là không phải POD. Bất kỳ loại nào được xác định bởi mã trước khi phát minh ra C ++ sẽ nhất thiết phải là POD, vì vậy tôi không nghĩ khả năng tương thích ngược sẽ là một vấn đề ở đó. Có những lợi thế nào khi có các loại không phải POD được khai báo structthay vì class?
supercat

1
@supercat Hơi muộn một chút nhưng hóa ra, thực hiện structclasslàm gần như điều tương tự là một quyết định thiết kế tuyệt vời mà giờ đây cho phép một tính năng gọn gàng gọi là "metaclass" (từ Herb) .
Rakete1111

@ Rakete1111: Thoạt nhìn, đề xuất đó có vẻ như nó xử lý trước một phiên bản ngôn ngữ sử dụng các từ khóa có tiền tố như thế nào $class. Tuy nhiên, tôi không chắc điều đó có liên quan classstructlà từ đồng nghĩa.
supercat

@supercat Hệ thống loại sẽ được phân chia nhiều hơn. Bằng cách có classstructcó nghĩa là cùng một cách hiệu quả, bạn có thể thực hiện các phép biến đổi tùy ý trên chúng ( $class) mà không phải lo lắng về việc thực hiện classa structvà ngược lại.
Rakete1111

@ Rakete1111: Nếu một số loại hoạt động và biến đổi nhất định an toàn với một số loại nhưng không phải loại khác, có loại xác định trực tiếp điều đó và có trình biên dịch từ chối các hoạt động và biến đổi không an toàn, có vẻ tốt hơn so với thay đổi đối với siêu dữ liệu được sử dụng trong những cách chỉ phù hợp với PODS, hãy âm thầm thay đổi thành không phải PODS.
supercat

53

Từ C ++ FQA Lite :

[16.4] Tại sao tôi nên sử dụng mới thay vì malloc cũ đáng tin cậy ()?

FAQ: mới / xóa gọi hàm tạo / hàm hủy; mới là loại an toàn, malloc thì không; mới có thể bị ghi đè bởi một lớp.

FQA: Những ưu điểm mới được hỏi bởi FAQ không phải là đức tính, bởi vì các nhà xây dựng, công cụ phá hủy và quá tải toán tử là rác (xem điều gì xảy ra khi bạn không có bộ sưu tập rác?), Và vấn đề an toàn loại rất nhỏ ở đây (thông thường bạn có để bỏ khoảng trống * được trả về bởi malloc sang loại con trỏ bên phải để gán nó cho một biến con trỏ được gõ, điều này có thể gây khó chịu, nhưng khác xa với "không an toàn").

Ồ, và sử dụng malloc cũ đáng tin cậy làm cho nó có thể sử dụng realloc đáng tin cậy & cũ không kém. Quá tệ, chúng tôi không có một nhà điều hành mới sáng bóng hoặc một cái gì đó.

Tuy nhiên, mới không đủ tệ để biện minh cho sự sai lệch so với phong cách phổ biến được sử dụng trong toàn bộ ngôn ngữ, ngay cả khi ngôn ngữ là C ++. Cụ thể, các lớp với các nhà xây dựng không tầm thường sẽ hoạt động sai theo cách gây tử vong nếu bạn chỉ đơn giản là vồ vập các đối tượng. Vậy tại sao không sử dụng mới trong suốt mã? Mọi người hiếm khi quá tải nhà điều hành mới, vì vậy có lẽ nó sẽ không cản trở bạn quá nhiều. Và nếu họ làm quá tải mới, bạn luôn có thể yêu cầu họ dừng lại.

Xin lỗi, tôi không thể cưỡng lại. :)


7
Đó là một cuộc bạo loạn ! Cảm ơn.
dmckee --- ex-moderator mèo con

8
Tôi không thể nghiêm túc nhận xét này vì nó rõ ràng dự kiến ​​thiên vị của tác giả chống lại C ++. C ++ là ngôn ngữ được sử dụng để tạo phần mềm định hướng hiệu suất và trình thu gom rác chỉ có thể gây bất lợi cho mục tiêu của nó. Tôi không đồng ý với toàn bộ câu trả lời của bạn!
Miguel

1
@Miguel Bạn đã bỏ lỡ trò đùa.
Dan Bechard

50

Luôn sử dụng mới trong C ++. Nếu bạn cần một khối bộ nhớ chưa được xử lý, bạn có thể sử dụng toán tử mới trực tiếp:

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

3
Thật thú vị, tôi luôn chỉ phân bổ một mảng char không dấu khi tôi cần một bộ đệm dữ liệu thô như thế này.
Greg Rogers

Cẩn thận các ngữ nghĩa nên như thế này: p_var = new type (bộ khởi tạo); Không phải kích thước.
Brian R. Bondy

11
Không phải nếu bạn gọi toán tử mới trực tiếp, thì nó sẽ lấy số byte để phân bổ làm tham số.
Ferruccio

1
Hrm không chắc chắn, tôi chưa bao giờ nghe về cú pháp này.
Brian R. Bondy

9
Ngược lại operator newoperator delete. Nó không phải là một hành động được xác định rõ để gọi deletemột biểu thức với kiểu void*.
CB Bailey

33

Sử dụng mallocchỉ để phân bổ bộ nhớ sẽ được quản lý bởi các thư viện và API trung tâm. Sử dụng và (và các biến thể) cho mọi thứ mà bạn kiểm soát.free newdelete[]


10
Cũng lưu ý rằng thư viện C được viết tốt sẽ ẩn malloc và miễn phí trong nội bộ, đây là cách lập trình viên C nên hoạt động.
Dacav

@dmckee bạn có ví dụ về C ++ bằng thư viện c-centric của malloc và miễn phí không?
milesma

1
@Dacav: Nếu một hàm C sẽ chấp nhận một con trỏ tới một đối tượng mà nó sẽ cần tiếp tục sử dụng sau khi hàm trả về và người gọi sẽ không có cách nào biết khi nào đối tượng vẫn cần thiết, thì nó sẽ hoàn toàn hợp lý cho hàm để xác định rằng con trỏ phải được tạo bằng malloc. Tương tự như vậy nếu một hàm như strdupcần tạo một đối tượng và trả nó lại cho người gọi, việc xác định rằng người gọi phải gọi freeđối tượng khi không còn cần thiết là hoàn toàn hợp lý . Làm thế nào các chức năng như vậy có thể tránh phơi bày việc sử dụng malloc / miễn phí cho người gọi?
supercat

@supercat, có một cái gì đó vốn đã sai khi có một hàm C chấp nhận một con trỏ tới các đối tượng, vì C hoàn toàn không biết về các đối tượng. Nói chung, tôi tin rằng cách tiếp cận tốt nhất là có các hàm bao bọc ngữ nghĩa xung quanh phân bổ / phân bổ cũng ở C. Nó vẫn có thể được chấp nhận, nhưng kém linh hoạt hơn, nếu một thư viện C đang yêu cầu người gọi phân bổ trước và / hoặc giải phóng bộ nhớ. Nếu một hàm C đang thực hiện điều này và yêu cầu quyền sở hữu trên bộ nhớ được phân bổ, bạn hoàn toàn bắt buộc phải phân bổ nó với malloc.
Dacav

@supercat Một ví dụ về gói hàng ngày mà tôi chắc rằng mọi người đã sử dụng là libgmp. Nếu bạn đã từng sử dụng bất kỳ phần mềm hoặc mã hóa nguồn mở nào dựa trên mã hóa đó (rất có thể) thì có lẽ bạn đã sử dụng một thư viện số học chính xác tùy ý cần phát triển và thu nhỏ dữ liệu nội bộ của chính nó. Điều này được thực hiện bằng cách sử dụng chức năng khởi tạo ... và sau đó bạn phải tự hỏi, làm thế nào để bạn sử dụng mã C là libgmp, trong C ++, mà không biên dịch lại nó trong C ++? Bây giờ với điều đó (linker) trong tâm trí, suy nghĩ về nó ... tại sao bất kỳ người nào hợp lý bao giờ đặt malloctrong C ++?
tự kỷ

31

mới vs malloc ()

1) newlà một toán tử , trong khi malloc()là một hàm .

2) newgọi các nhà xây dựng , trong khi malloc()không.

3) newtrả về kiểu dữ liệu chính xác , trong khi malloc()trả về void * .

4) newkhông bao giờ trả lại NULL (sẽ thất bại) trong khi malloc()trả về NULL

5) Tái phân bổ bộ nhớ không được xử lý newtrong khi malloc()có thể


6
Xin chào, Đối với điểm 4), mới có thể được hướng dẫn trả lại NULL khi thất bại. char* ptr = new (std::nothrow) char [323232];
Singh

1
6) tạo mới từ các đối số của hàm tạo, trong khi malloc sử dụng kích thước.
Evan Moran

cũng có một newchức năng
Ma Ming

Nếu bạn đã rất nghiêng về C khi tái phân bổ , tôi sẽ hy vọng rằng bạn sẽ sử dụng reallocchứ không phải mallocvà bắt đầu với biến con trỏ của bạn được khởi tạo NULL. Nếu bạn muốn một đoạn bộ nhớ có thể thay đổi kích thước trong C ++, mặt khác, tôi sẽ đề xuất std::vectortrái ngược với realloc... Điều đó hoặc một tệp.
tự kỷ

19

Để trả lời câu hỏi của bạn, bạn nên biết sự khác biệt giữa mallocnew . Sự khác biệt rất đơn giản:

malloc cấp phát bộ nhớ , trong khi new phân bổ bộ nhớ VÀ gọi hàm tạo của đối tượng bạn phân bổ bộ nhớ cho.

Vì vậy, trừ khi bạn bị giới hạn ở C, bạn không bao giờ nên sử dụng malloc, đặc biệt là khi làm việc với các đối tượng C ++. Đó sẽ là một công thức để phá vỡ chương trình của bạn.

Ngoài ra sự khác biệt giữa freedeletelà khá giống nhau. Sự khác biệt là deletesẽ gọi hàm hủy của đối tượng của bạn ngoài việc giải phóng bộ nhớ.


13

Có một sự khác biệt lớn giữa mallocnew. mallocphân bổ bộ nhớ. Điều này tốt cho C, vì trong C, một cục bộ nhớ là một đối tượng.

Trong C ++, nếu bạn không xử lý các loại POD (tương tự như các loại C), bạn phải gọi một hàm tạo trên một vị trí bộ nhớ để thực sự có một đối tượng ở đó. Các loại không phải POD rất phổ biến trong C ++, vì nhiều tính năng của C ++ làm cho một đối tượng tự động không phải là POD.

newcấp phát bộ nhớ tạo một đối tượng trên vị trí bộ nhớ đó. Đối với các loại không phải POD, điều này có nghĩa là gọi hàm tạo.

Nếu bạn làm một cái gì đó như thế này:

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

Con trỏ bạn nhận được không thể bị hủy đăng ký vì nó không trỏ đến một đối tượng. Bạn cần gọi một nhà xây dựng trên nó trước khi bạn có thể sử dụng nó (và điều này được thực hiện bằng cách sử dụng vị trí new).

Mặt khác, nếu bạn làm:

non_pod_type* p = new non_pod_type();

Bạn nhận được một con trỏ luôn hợp lệ, bởi vì newđã tạo một đối tượng.

Ngay cả đối với các loại POD, có một sự khác biệt đáng kể giữa hai loại:

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

Đoạn mã này sẽ in một giá trị không xác định, vì các đối tượng POD được tạo bởi mallockhông được khởi tạo.

Với new, bạn có thể chỉ định một hàm tạo để gọi và do đó có được một giá trị được xác định rõ.

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

Nếu bạn thực sự muốn nó, bạn có thể sử dụng newđể có được các đối tượng POD chưa được khởi tạo. Xem câu trả lời khác này để biết thêm thông tin về điều đó.

Một sự khác biệt khác là hành vi khi thất bại. Khi nó không phân bổ bộ nhớ, malloctrả về một con trỏ null, trong khi newném một ngoại lệ.

Cái trước yêu cầu bạn kiểm tra mọi con trỏ được trả về trước khi sử dụng nó, trong khi cái sau sẽ luôn tạo ra các con trỏ hợp lệ.

Vì những lý do này, trong mã C ++, bạn nên sử dụng new, và không malloc. Nhưng ngay cả khi đó, bạn không nên sử dụng new"trong mở", vì nó có được các tài nguyên bạn cần phát hành sau này. Khi bạn sử dụng, newbạn nên chuyển ngay kết quả của nó vào một lớp quản lý tài nguyên:

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

7

Phân bổ động chỉ được yêu cầu khi thời gian sống của đối tượng phải khác với phạm vi được tạo trong (Điều này cũng giúp cho phạm vi nhỏ hơn khi lớn hơn) và bạn có một lý do cụ thể trong đó lưu trữ theo giá trị không công việc.

Ví dụ:

 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);

Từ C ++ 11 trở đi, chúng ta đã std::unique_ptrxử lý bộ nhớ được phân bổ, trong đó có quyền sở hữu bộ nhớ được phân bổ. std::shared_ptrđược tạo ra khi bạn phải chia sẻ quyền sở hữu. (bạn sẽ cần điều này ít hơn bạn mong đợi trong một chương trình tốt)

Tạo một cá thể trở nên thực sự dễ dàng:

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 cũng cho biết thêm std::optionalcó thể ngăn bạn yêu cầu phân bổ bộ nhớ

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

Ngay khi 'cá thể' đi ra khỏi phạm vi, bộ nhớ sẽ được dọn sạch. Chuyển quyền sở hữu cũng dễ dàng:

 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)

Vậy khi nào bạn vẫn cần new? Hầu như không bao giờ từ C ++ 11 trở đi. Hầu hết các bạn sử dụng std::make_uniquecho đến khi bạn đạt đến điểm mà bạn đạt được một API chuyển quyền sở hữu thông qua các con trỏ thô.

 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

Trong C ++ 98/03, bạn phải thực hiện quản lý bộ nhớ thủ công. Nếu bạn ở trong trường hợp này, hãy thử nâng cấp lên phiên bản mới hơn của tiêu chuẩn. Nếu bạn bị mắc kẹt:

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

Hãy chắc chắn rằng bạn theo dõi quyền sở hữu một cách chính xác để không có bất kỳ rò rỉ bộ nhớ! Di chuyển ngữ nghĩa cũng không hoạt động.

Vậy, khi nào chúng ta cần malloc trong C ++? Lý do hợp lệ duy nhất sẽ là phân bổ bộ nhớ và khởi tạo nó sau thông qua vị trí mới.

 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

Mặc dù, những điều trên là hợp lệ, điều này cũng có thể được thực hiện thông qua một nhà điều hành mới. std::vectorlà một ví dụ tốt cho việc này.

Cuối cùng, chúng tôi vẫn còn con voi trong phòng : C. Nếu bạn phải làm việc với thư viện C nơi bộ nhớ được cấp phát trong mã C ++ và được giải phóng trong mã C (hoặc ngược lại), bạn buộc phải sử dụng malloc / miễn phí.

Nếu bạn ở trong trường hợp này, hãy quên các hàm ảo, hàm thành viên, lớp ... Chỉ cho phép các cấu trúc có POD trong đó.

Một số ngoại lệ cho các quy tắc:

  • Bạn đang viết một thư viện chuẩn với các cấu trúc dữ liệu nâng cao trong đó malloc phù hợp
  • Bạn phải phân bổ số lượng lớn bộ nhớ (Trong bản sao bộ nhớ của tệp 10 GB?)
  • Bạn có công cụ ngăn bạn sử dụng các cấu trúc nhất định
  • Bạn cần lưu trữ một loại không đầy đủ

6

Có một vài điều newkhông malloclàm được:

  1. new xây dựng đối tượng bằng cách gọi hàm tạo của đối tượng đó
  2. new không yêu cầu typecasting của bộ nhớ được phân bổ.
  3. Nó không yêu cầu một lượng bộ nhớ được phân bổ, thay vào đó nó đòi hỏi một số đối tượng được xây dựng.

Vì vậy, nếu bạn sử dụng malloc, thì bạn cần phải làm những điều trên một cách rõ ràng, điều này không phải lúc nào cũng thực tế. Ngoài ra, newcó thể bị quá tải nhưng mallockhông thể.


5

Nếu bạn làm việc với dữ liệu không cần xây dựng / hủy và yêu cầu phân bổ lại (ví dụ: một mảng lớn ints), thì tôi tin rằng malloc / free là một lựa chọn tốt vì nó cung cấp cho bạn realloc, nhanh hơn so với memcpy mới -delete (nó nằm trong hộp Linux của tôi, nhưng tôi đoán đây có thể phụ thuộc vào nền tảng). Nếu bạn làm việc với các đối tượng C ++ không phải là POD và yêu cầu xây dựng / hủy, thì bạn phải sử dụng các toán tử mới và xóa.

Dù sao, tôi không hiểu lý do tại sao bạn không nên sử dụng cả hai (với điều kiện bạn giải phóng bộ nhớ bị lừa và xóa các đối tượng được phân bổ mới) nếu có thể tận dụng tốc độ tăng tốc (đôi khi là đáng kể, nếu bạn đang phân bổ lại các mảng lớn của POD) mà realloc có thể cung cấp cho bạn.

Trừ khi bạn cần nó, mặc dù vậy, bạn nên giữ mới / xóa trong C ++.


3

Nếu bạn có mã C mà bạn muốn chuyển sang C ++, bạn có thể để lại bất kỳ lệnh gọi malloc () nào trong đó. Đối với bất kỳ mã C ++ mới nào, tôi khuyên bạn nên sử dụng mã mới thay thế.


3

Nếu bạn đang sử dụng C ++, hãy thử sử dụng new / xóa thay vì malloc / calloc vì chúng là toán tử. Đối với malloc / calloc, bạn cần bao gồm một tiêu đề khác. Đừng trộn hai ngôn ngữ khác nhau trong cùng một mã. Công việc của họ tương tự nhau về mọi mặt, cả hai đều phân bổ bộ nhớ động từ phân đoạn heap trong bảng băm.


2

new sẽ khởi tạo các giá trị mặc định của struct và liên kết chính xác các tham chiếu trong nó với chính nó.

Ví dụ

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

Vì vậy, new struct test_ssẽ trả về một cấu trúc được khởi tạo với một tham chiếu hoạt động, trong khi phiên bản malloc'ed không có giá trị mặc định và các tham chiếu thực tế không được khởi tạo.


1

Từ góc nhìn thấp hơn, mới sẽ khởi tạo tất cả bộ nhớ trước khi đưa ra bộ nhớ trong khi malloc sẽ giữ nội dung ban đầu của bộ nhớ.


4
mới không có trong bộ nhớ khởi tạo chung, mặc dù có nhiều cách để thực hiện điều đó: xem stackoverflow.com/questions/2204176/ cho một cuộc thảo luận về nó.
wjl

0

Trong trường hợp sau, chúng ta không thể sử dụng mới vì nó gọi hàm tạo.

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

Các toán tử newdeletetoán tử có thể hoạt động trên các lớp và cấu trúc, trong khi mallocfreechỉ hoạt động với các khối bộ nhớ cần được truyền.

Việc sử dụng new/deletesẽ giúp cải thiện mã của bạn vì bạn sẽ không cần truyền bộ nhớ được phân bổ vào cấu trúc dữ liệu cần thiết.


0

Trường hợp hiếm khi cân nhắc sử dụng malloc / free thay vì new / xóa là khi bạn phân bổ và sau đó phân bổ lại (các loại pod đơn giản, không phải đối tượng) bằng realloc vì không có chức năng tương tự như realloc trong C ++ (mặc dù điều này có thể được thực hiện bằng cách sử dụng tiếp cận C ++ nhiều hơn).


-4

malloc () được sử dụng để gán động bộ nhớ trong C trong khi công việc tương tự được thực hiện bởi new () trong c ++. Vì vậy, bạn không thể trộn các quy ước mã hóa của 2 ngôn ngữ. Sẽ tốt hơn nếu bạn yêu cầu sự khác biệt giữa calloc và malloc ()


2
Bạn có thể (nhưng hầu như luôn luôn không nên) sử dụng malloctrong C ++.
interjay

1
Bạn cũng đã bỏ lỡ điểm chính mà bạn nên nhắm đến để tránh phân bổ bộ nhớ động, trừ khi thực hiện thông qua con trỏ thông minh. Bạn chỉ đang tự đặt mình vào nỗi đau khôn ngoan khác
thecoshman
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.