2011-09-16 31 views
56

Theo tiêu chuẩn ngôn ngữ tương ứng, C cung cấp phân bổ bộ nhớ động chỉ thông qua họ malloc(), trong khi ở C++, hình thức phân bổ phổ biến nhất được thực hiện bởi ::operator new(). Các malloc kiểu C cũng có sẵn trong C++, và nhiều ví dụ "cấp phát đầu tiên của em bé" sử dụng nó làm chức năng phân bổ lõi, nhưng tôi tò mò về cách trình biên dịch hiện đại thực hiện toán tử sản xuất mới.Phân bổ bộ nhớ động có khác biệt trong C và C++ trong các triển khai phổ biến không?

Đây có phải là một trình bao bọc mỏng xung quanh malloc() hoặc thực thi khác về cơ bản với hành vi phân bổ bộ nhớ khá khác với chương trình C++ điển hình so với chương trình C điển hình không?

[Edit: Tôi tin rằng sự khác biệt chính thường được mô tả như sau: Chương trình C có phân bổ ít hơn, lớn hơn và lâu hơn, trong khi chương trình C++ có nhiều phân bổ ngắn, ngắn ngủi. Cảm thấy tự do để kêu vang trong nếu đó là sai lầm, nhưng nó có vẻ như một trong những sẽ được hưởng lợi từ việc này vào tài khoản.]

Đối với một trình biên dịch như GCC nó sẽ được dễ dàng để chỉ có một phân bổ lõi đơn thực hiện và sử dụng cho tất cả có liên quan ngôn ngữ, vì vậy tôi tự hỏi liệu có sự khác biệt trong các chi tiết cố gắng tối ưu hóa hiệu suất phân bổ kết quả trong từng ngôn ngữ hay không.


Cập nhật: Cảm ơn tất cả các câu trả lời tuyệt vời! Có vẻ như trong GCC, điều này hoàn toàn được giải quyết bởi ptmalloc và MSVC cũng sử dụng malloc ở lõi. Có ai biết làm thế nào MSVC-malloc được thực hiện?

+1

(Tôi rất sẵn lòng nghe về các trình biên dịch không phải của GCC, nếu có ai đó có bất kỳ thông tin chi tiết nào.) –

Trả lời

48

Đây là việc thực hiện sử dụng bởi g++ 4.6.1:

_GLIBCXX_WEAK_DEFINITION void * 
operator new (std::size_t sz) throw (std::bad_alloc) 
{ 
    void *p; 

    /* malloc (0) is unpredictable; avoid it. */ 
    if (sz == 0) 
    sz = 1; 
    p = (void *) malloc (sz); 
    while (p == 0) 
    { 
     new_handler handler = __new_handler; 
     if (! handler) 
#ifdef __EXCEPTIONS 
     throw bad_alloc(); 
#else 
     std::abort(); 
#endif 
     handler(); 
     p = (void *) malloc (sz); 
    } 

    return p; 
} 

này được tìm thấy trong libstdc++-v3/libsupc++/new_op.cc bên trong g ++ distro nguồn.

Như bạn có thể thấy, đó là một trình bao bọc khá mỏng xung quanh malloc.

chỉnh sửa Trên nhiều hệ thống, bạn có thể tinh chỉnh hành vi của malloc, thường bằng cách gọi mallopt hoặc đặt biến môi trường. Dưới đây là một số article thảo luận về một số tính năng có sẵn trên Linux.

According to Wikipedia, glibc phiên bản 2.3 + sử dụng một phiên bản sửa đổi của bộ cấp phát gọi ptmalloc, mà chính nó là một dẫn xuất của dlmalloc thiết kế bởi Doug Lea. Điều thú vị là, trong một article about dlmalloc Doug Lea cung cấp cho các quan điểm sau đây (tôi nhấn mạnh):

tôi đã viết phiên bản đầu tiên của bộ cấp phát sau khi viết một số chương trình C++ rằng hầu như chỉ dựa vào phân bổ bộ nhớ động. Tôi thấy rằng chúng chạy chậm hơn và/hoặc với tổng số tiêu thụ bộ nhớ nhiều hơn tôi mong đợi. Điều này là do các đặc điểm của bộ cấp phát bộ nhớ trên các hệ thống mà tôi đang chạy trên (chủ yếu là các phiên bản hiện tại của SunO và BSD). Để truy cập điều này, lúc đầu, tôi đã viết một số trình phân bổ có mục đích đặc biệt trong C++, thông thường bằng cách nạp chồng toán tử mới cho các lớp khác nhau. Một số chúng được mô tả trong một bài báo về kỹ thuật phân bổ C++ là được điều chỉnh vào bài viết C++ Báo cáo 1989 Một số kỹ thuật phân bổ lưu trữ cho các lớp chứa.

Tuy nhiên, tôi sớm nhận ra rằng việc xây dựng một cấp đặc biệt cho mỗi lớp mới mà có xu hướng được tự động phân bổ và sử dụng nhiều là không phải là một chiến lược tốt khi xây dựng các loại lập trình lớp hỗ trợ có mục đích chung tôi đang viết tại thời gian. (Từ năm 1986 đến năm 1991, tôi là tác giả chính của libg ++, thư viện GNU C++.) Cần có giải pháp rộng hơn - viết một người cấp phát đủ tốt dưới mức C++ và C bình thường để lập trình viên sẽ không bị cám dỗ để viết các nhà phân bổ có mục đích đặc biệt trừ các điều kiện đặc biệt là .

Bài viết này trình bày một số mục tiêu thiết kế chính, các thuật toán và cân nhắc triển khai cho trình phân bổ này.

+0

Cảm ơn bạn rất nhiều! Làm thế nào thú vị (và cũng hơi thất vọng). Có lẽ 'malloc()' chỉ là tốt như vậy mà nó xử lý hầu hết các tình huống với hiệu suất thỏa đáng. –

+1

@Kerrek SB: Nói thẳng thắn, trực giác của tôi không gợi ý rằng C và C++ có các mẫu phân bổ khác nhau. Điều đó nói rằng, trực giác của tôi có thể tất nhiên là sai. :-) – NPE

+0

@Kerrek: performnce ?? Nếu bạn chỉ cần loại bỏ "fuzziness", trong trường hợp malloc không thất bại, mã trên chỉ trở thành ... p = malloc (...); if (! p) {} trả về p; Hãy xem xét thời gian hệ điều hành sẽ chi tiêu vào việc thực hiện malloc, tôi không thấy làm thế nào nếu (! P) (trong assembler chỉ là một JZ intruction) có thể thay đổi performace! –

15

Trong hầu hết các lần triển khai operator new() chỉ cần gọi malloc(). Trong thực tế, ngay cả tiêu chuẩn suggests that as a default stratege. Tất nhiên bạn có thể thực hiện operator new của riêng bạn, thường là cho một lớp học nếu bạn muốn hiệu suất tốt hơn, nhưng mặc định thường chỉ cần gọi malloc().

+0

Thực ra, tôi sẽ tìm mã nguồn cho điều đó ở GCC ở đâu? Đây không phải là một phần của tiêu đề. Có phải nó nằm trong nguồn libstdC++ ở đâu đó không? –

+0

@Kerrek SB: Một bằng chứng gián tiếp là 'miễn phí (char mới) 'có vẻ chạy ổn. – sharptooth

+0

rất nghịch ngợm :-) Đó là tò mò mặc dù hai ngôn ngữ khá khác nhau có thể được hài lòng bởi các thuật toán phân bổ chính xác giống nhau. Có lẽ chỉ nói đến chất lượng của thiết kế 'malloc()' ... –

2

Nó không phải là một vấn đề hiệu suất: pA = new A có một tác dụng phụ khác với pA = (A*)malloc(sizeof(A));

Trong một giây, của Một constructor không được gọi. Để đi đến một tác dụng tương tự bạn nên làm

pA = (A*)malloc(sizeof(A)); 
new(pA)A(); 

nơi mới là "vị trí mới" ...

void* operator new(size_t sz, void* place) 
{ return place; } 
+7

Tôi không hỏi về biểu thức 'mới'. Tôi cũng nhận thức được khái niệm ngôn ngữ C++ cốt lõi. Tôi đang hỏi cụ thể về ':: operator new()' (mặc định, không phải là vị trí một), là hàm phân bổ được sử dụng bởi biểu thức 'mới'. –

12

glibc điều hành mới là một wrapper mỏng xung quanh malloc. Và glibc malloc sử dụng các chiến lược khác nhau cho các yêu cầu phân bổ kích thước khác nhau. Bạn có thể xem triển khai hoặc ít nhất là các ý kiến ​​here.

Dưới đây là một đoạn trích từ các ý kiến ​​trong malloc.c:

/* 
47 This is not the fastest, most space-conserving, most portable, or 
48 most tunable malloc ever written. However it is among the fastest 
49 while also being among the most space-conserving, portable and tunable. 
50 Consistent balance across these factors results in a good general-purpose 
51 allocator for malloc-intensive programs. 
52 
53 The main properties of the algorithms are: 
54 * For large (>= 512 bytes) requests, it is a pure best-fit allocator, 
55  with ties normally decided via FIFO (i.e. least recently used). 
56 * For small (<= 64 bytes by default) requests, it is a caching 
57  allocator, that maintains pools of quickly recycled chunks. 
58 * In between, and for combinations of large and small requests, it does 
59  the best it can trying to meet both goals at once. 
60 * For very large requests (>= 128KB by default), it relies on system 
61  memory mapping facilities, if supported. 
*/ 
+0

Gọn gàng, cảm ơn bạn! –

10

Mở Visual C++, bước vào một biểu new dẫn tôi đến đoạn này trong new.cpp:

#include <cstdlib> 
#include <new> 

_C_LIB_DECL 
int __cdecl _callnewh(size_t size) _THROW1(_STD bad_alloc); 
_END_C_LIB_DECL 

void *__CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc) 
     {  // try to allocate size bytes 
     void *p; 
     while ((p = malloc(size)) == 0) 
       if (_callnewh(size) == 0) 
       {  // report no memory 
       static const std::bad_alloc nomem; 
       _RAISE(nomem); 
       } 

     return (p); 
     } 

Vì vậy VC++ 's new cũng kết thúc cuộc gọi malloc().

+1

Tuyệt vời, cảm ơn bạn! Tôi tự hỏi mà 'malloc()' VC sử dụng. –

+0

Việc thực hiện malloc của VC thực sự là wrapper mỏng xung quanh RtlAllocateHeap trong ntdll.dll. – newgre

Các vấn đề liên quan