Tại sao `free` trong C không nhận số byte được giải phóng?


84

Chỉ cần nói rõ: Tôi biết điều đó mallocfreeđược triển khai trong thư viện C, thư viện này thường phân bổ nhiều phần bộ nhớ từ HĐH và thực hiện quản lý riêng của nó để phân bổ nhiều bộ nhớ nhỏ hơn cho ứng dụng và theo dõi số byte được cấp . Câu hỏi này không phải là Làm thế nào miễn phí biết bao nhiêu miễn phí .

Đúng hơn, tôi muốn biết tại sao lại freeđược làm theo cách này ngay từ đầu. Là một ngôn ngữ cấp thấp, tôi nghĩ sẽ hoàn toàn hợp lý nếu yêu cầu một lập trình viên C theo dõi không chỉ bộ nhớ đã được cấp phát mà còn bao nhiêu (trên thực tế, tôi thường thấy rằng tôi thường theo dõi số byte bất ổn). Tôi cũng nhận ra rằng việc đưa ra số byte một cách rõ ràng freecó thể cho phép một số tối ưu hóa hiệu suất, ví dụ: một trình phân bổ có các nhóm riêng biệt cho các kích thước phân bổ khác nhau sẽ có thể xác định nhóm nào cần giải phóng chỉ bằng cách xem các đối số đầu vào và sẽ có ít không gian hơn trên tổng thể.

Vì vậy, tóm lại, tại sao được mallocfreeđược tạo ra để chúng được yêu cầu theo dõi nội bộ số lượng byte được phân bổ? Nó chỉ là một tai nạn lịch sử?

Một chỉnh sửa nhỏ: Một vài người đã cung cấp các điểm như "điều gì sẽ xảy ra nếu bạn miễn phí một số tiền khác với số tiền bạn đã phân bổ". API tưởng tượng của tôi có thể đơn giản yêu cầu một API giải phóng chính xác số byte được phân bổ; giải phóng nhiều hơn hoặc ít hơn có thể chỉ đơn giản là UB hoặc việc thực hiện được xác định. Tuy nhiên, tôi không muốn ngăn cản thảo luận về các khả năng khác.


18
Bởi vì việc tự theo dõi các phân bổ đã là một điều khó khăn và nó thậm chí sẽ làm phức tạp mã hơn nếu bạn cũng phải theo dõi kích thước.
Jens Gustedt

11
Tôi có thể nghĩ ra một số lý do: Tại sao lại bắt người dùng làm điều đó nếu họ không cần? Điều gì sẽ xảy ra nếu người dùng làm rối nó lên? Dù sao thì đó cũng là một câu hỏi thừa. Nếu họ đưa ra lựa chọn khác, bạn vẫn sẽ hỏi tại sao.
BoBTFish

15
@BoBTFish: Đây là C mà chúng ta đang nói đến, không phải Python hay thậm chí là C ++. Người dùng đã phải thực hiện $ h! 1 tấn mà anh ta không cần phải làm. Đó không phải là một lý do.
user541686

4
K&R cũng không có gì để nói về điều này. Chúng ta có thể suy đoán tất cả những gì chúng ta thích, nhưng tôi nghĩ lý do ban đầu có thể bị mất trong lịch sử .
BoBTFish

35
Bạn không thể yêu cầu lập trình viên chuyển đúng kích thước của khối, bởi vì người gọi mallockhông biết kích thước của khối được trả về . mallocthường trả về một khối lớn hơn yêu cầu. Tốt nhất, người lập trình có thể chuyển kích thước được yêu cầu trong malloc()cuộc gọi, điều này sẽ không giúp ích gì cho người triển khai free()cả.
Ben Voigt

Câu trả lời:


97

Một đối số free(void *)(được giới thiệu trong Unix V7) có một lợi thế lớn khác so với hai đối số mfree(void *, size_t)trước đó mà tôi chưa thấy đề cập ở đây: một đối số freeđơn giản hóa đáng kể mọi API khác hoạt động với bộ nhớ heap. Ví dụ, nếu freecần kích thước của khối bộ nhớ, thì strdupbằng cách nào đó sẽ phải trả về hai giá trị (con trỏ + kích thước) thay vì một (con trỏ) và C làm cho trả về nhiều giá trị cồng kềnh hơn nhiều so với trả về một giá trị. Thay vì char *strdup(char *)chúng ta phải viết char *strdup(char *, size_t *)hoặc viết khác struct CharPWithSize { char *val; size_t size}; CharPWithSize strdup(char *). (Ngày nay, tùy chọn thứ hai đó trông khá hấp dẫn, bởi vì chúng tôi biết rằng chuỗi kết thúc bằng NUL là "lỗi thiết kế thảm khốc nhất trong lịch sử máy tính", nhưng đó là cách nói nhận thức. Trở lại những năm 70, khả năng xử lý các chuỗi đơn giản của C char *thực sự được coi là một lợi thế xác định so với các đối thủ cạnh tranh như Pascal và Algol .) Thêm vào đó, nó không chỉ strdupgặp phải vấn đề này - nó ảnh hưởng đến mọi hệ thống hoặc do người dùng định nghĩa hàm phân bổ bộ nhớ heap.

Các nhà thiết kế Unix ban đầu là những người rất thông minh, và có nhiều lý do tại sao lại freetốt hơn mfreevề cơ bản, tôi nghĩ câu trả lời cho câu hỏi là họ nhận thấy điều này và thiết kế hệ thống của họ cho phù hợp. Tôi nghi ngờ bạn sẽ tìm thấy bất kỳ bản ghi trực tiếp nào về những gì đang diễn ra trong đầu họ vào thời điểm họ đưa ra quyết định đó. Nhưng chúng ta có thể tưởng tượng.

Giả sử bạn đang viết ứng dụng trong C để chạy trên V6 Unix, với hai đối số của nó mfree. Cho đến nay, bạn đã quản lý tốt, nhưng việc theo dõi các kích thước con trỏ này ngày càng trở nên phức tạp hơn khi các chương trình của bạn trở nên tham vọng hơn và ngày càng yêu cầu sử dụng nhiều hơn các biến được phân bổ theo đống. Nhưng sau đó bạn có một ý tưởng tuyệt vời: thay vì sao chép những thứ này size_tmọi lúc, bạn có thể chỉ cần viết một số hàm tiện ích, lưu trữ kích thước trực tiếp bên trong bộ nhớ được cấp phát:

void *my_alloc(size_t size) {
    void *block = malloc(sizeof(size) + size);
    *(size_t *)block = size;
    return (void *) ((size_t *)block + 1);
}
void my_free(void *block) {
    block = (size_t *)block - 1;
    mfree(block, *(size_t *)block);
}

Và bạn càng viết nhiều mã bằng cách sử dụng các chức năng mới này, chúng càng có vẻ tuyệt vời hơn. Chúng không chỉ làm cho mã của bạn dễ viết hơn mà còn làm cho mã của bạn nhanh hơn - hai điều không thường đi cùng nhau! Trước khi bạn chuyển các ký tự này đi size_tkhắp nơi, điều này làm tăng chi phí CPU cho quá trình sao chép và có nghĩa là bạn phải đổ các thanh ghi thường xuyên hơn (đặc biệt là đối với các đối số hàm bổ sung) và lãng phí bộ nhớ (vì các lệnh gọi hàm lồng nhau thường sẽ dẫn đến trong nhiều bản sao size_tđang được lưu trữ trong các khung ngăn xếp khác nhau). Trong hệ thống mới của bạn, bạn vẫn phải dành bộ nhớ để lưu trữsize_t, nhưng chỉ một lần và nó không bao giờ bị sao chép ở bất kỳ đâu. Đây có vẻ như là những hiệu quả nhỏ, nhưng hãy nhớ rằng chúng ta đang nói về các máy cao cấp với 256 KiB RAM.

Điều này làm cho bạn hạnh phúc! Vì vậy, bạn chia sẻ mẹo hay của mình với những đấng mày râu đang làm việc trên bản phát hành Unix tiếp theo, nhưng nó không làm họ hài lòng mà còn khiến họ buồn. Bạn thấy đấy, họ chỉ đang trong quá trình thêm một loạt các chức năng tiện ích mới như strdup, và họ nhận ra rằng những người sử dụng mẹo hay của bạn sẽ không thể sử dụng các chức năng mới của họ, bởi vì các chức năng mới của họ đều sử dụng con trỏ + kích thước cồng kềnh API. Và điều đó cũng khiến bạn buồn, vì bạn nhận ra rằng mình sẽ phải tự viết lại strdup(char *)hàm tốt trong mỗi chương trình bạn viết, thay vì có thể sử dụng phiên bản hệ thống.

Nhưng đợi đã! Đây là năm 1977 và khả năng tương thích ngược sẽ không được phát minh trong 5 năm nữa! Và bên cạnh đó, không ai nghiêm túc thực sự sử dụng thứ "Unix" khó hiểu này với cái tên không màu sắc của nó. Ấn bản đầu tiên của K&R hiện đang trên đường đến nhà xuất bản, nhưng điều đó không thành vấn đề - nó nói ngay trên trang đầu tiên rằng "C không cung cấp hoạt động nào để xử lý trực tiếp các đối tượng tổng hợp như chuỗi ký tự ... không có đống ... ”. Tại thời điểm này trong lịch sử, string.hmalloclà phần mở rộng của nhà cung cấp (!). Vì vậy, Be râu Man # 1 đề xuất, chúng ta có thể thay đổi chúng theo cách nào chúng ta muốn; tại sao chúng tôi không tuyên bố người phân bổ khó khăn của bạn là người phân bổ chính thức ?

Vài ngày sau, Be râu Man # 2 nhìn thấy API mới và nói này, chờ đã, điều này tốt hơn trước, nhưng nó vẫn dành toàn bộ từ mỗi phân bổ để lưu trữ kích thước. Anh ta xem đây là điều tiếp theo để báng bổ. Mọi người khác nhìn anh ấy như thể anh ấy bị điên, vì bạn còn có thể làm gì nữa? Đêm đó, anh ấy ở lại muộn và phát minh ra một trình phân bổ mới không lưu trữ kích thước nào cả, nhưng thay vào đó, suy diễn nó nhanh chóng bằng cách thực hiện các phép bit ma thuật đen trên giá trị con trỏ và hoán đổi nó trong khi giữ nguyên API mới. API mới có nghĩa là không ai nhận thấy sự chuyển đổi, nhưng họ nhận thấy rằng sáng hôm sau trình biên dịch sử dụng RAM ít hơn 10%.

Và bây giờ mọi người đều hạnh phúc: Bạn nhận được mã dễ viết hơn và nhanh hơn, Người đàn ông có râu số 1 viết một cách đơn giản strdupmà mọi người sẽ thực sự sử dụng và Người đàn ông có râu số 2 - tự tin rằng anh ấy đã kiếm được tiền của mình - - quay lại với việc lộn xộn với quines . Gửi nó!

Hoặc ít nhất, đó là cách nó có thể xảy ra.


14
Err, chỉ trong trường hợp nó không rõ ràng, đây là một chuyến bay lạ mắt, với các chi tiết chứng thực được đưa vào để cung cấp sự đa dạng nghệ thuật. Bất kỳ sự giống nhau nào với những người sống hoặc đã chết hoàn toàn là do mọi người liên quan đều trông giống nhau . Xin đừng nhầm lẫn với lịch sử thực tế.
Nathaniel J. Smith

5
Bạn thắng. Với tôi, đây là lời giải thích hợp lý nhất (và là tác phẩm hay nhất). Ngay cả khi mọi thứ ở đây được chứng minh là không chính xác hoặc không hợp lệ, đây là câu trả lời tốt nhất cho những mô tả xuất sắc về những người đàn ông có râu.
jaymmer - Phục hồi Monica

Chà, một câu trả lời trên trang này thực sự có vẻ hợp lý. +1 từ tôi.
dùng541686

Thậm chí tốt hơn - Một câu trả lời trên trang này thực sự thú vị! +1.
David C. Rankin

Tôi tự hỏi liệu có bất kỳ hệ thống Pascal đáng chú ý nào sử dụng nhóm chuỗi thu thập rác theo cách tương tự như trình thông dịch BASIC của máy tính vi mô không? Ngữ nghĩa của C sẽ không hoạt động với một thứ như vậy, nhưng trong Pascal, một thứ như vậy có thể được xử lý khá tốt nếu mã duy trì các khung ngăn xếp có thể theo dõi (điều mà nhiều trình biên dịch đã làm).
supercat

31

"Tại sao freetrong C không lấy số byte được giải phóng?"

Bởi vì nó không cần thiết, và nó sẽ không hoàn toàn có ý nghĩa .

Khi bạn phân bổ thứ gì đó, bạn muốn cho hệ thống biết cần phân bổ bao nhiêu byte (vì những lý do rõ ràng).

Tuy nhiên, khi bạn đã cấp phát đối tượng của mình, kích thước của vùng bộ nhớ bạn lấy lại sẽ được xác định. Nó ngầm. Đó là một khối bộ nhớ liền kề. Bạn không thể phân bổ một phần của nó (hãy quên realloc(), dù sao đó không phải là những gì nó đang làm), bạn chỉ có thể phân bổ toàn bộ. Bạn cũng không thể "phân bổ X byte" - bạn có thể giải phóng khối bộ nhớ mà bạn nhận được malloc()hoặc không.

Và bây giờ, nếu bạn muốn giải phóng nó, bạn chỉ cần nói với hệ thống quản lý bộ nhớ: "đây là con trỏ này, free()khối mà nó đang trỏ tới." - và trình quản lý bộ nhớ sẽ biết cách làm điều đó, bởi vì nó ngầm biết kích thước, hoặc vì nó thậm chí có thể không cần kích thước.

Ví dụ, hầu hết các triển khai điển hình của malloc()việc duy trì một danh sách liên kết các con trỏ tới các khối bộ nhớ được cấp phát và giải phóng. Nếu bạn chuyển một con trỏ tới free(), nó sẽ chỉ tìm kiếm con trỏ đó trong danh sách "được cấp phát", hủy liên kết nút tương ứng và đính kèm nó vào danh sách "miễn phí". Nó thậm chí không cần kích thước khu vực. Nó sẽ chỉ cần thông tin đó khi nó có khả năng cố gắng sử dụng lại khối được đề cập.


Nếu tôi vay bạn 100 đô la và sau đó vay lại bạn 100 đô la và sau đó làm điều đó năm lần nữa, bạn có thực sự quan tâm rằng tôi đã vay tiền bạn bảy lần không (trừ khi bạn thực sự đang tính lãi suất!) ?? Hay bạn chỉ quan tâm rằng tôi đã vay bạn 700 đô la? Điều tương tự ở đây: hệ thống chỉ quan tâm đến bộ nhớ chưa được cấp phát, nó không (và không cần và không nên) quan tâm đến cách bộ nhớ được cấp phát được phân chia.
user541686.

1
@Mehrdad: Không, và nó không. C, mặc dù, không. Toàn bộ mục đích của nó là làm cho mọi thứ (một chút) an toàn hơn. Tôi thực sự không biết bạn đang tìm gì ở đây.
Lightness Races in Orbit

12
@ user3477950: Nó không cần phải vượt qua số byte : Vâng, vì nó được thiết kế theo cách này. OP hỏi Tại sao nó được thiết kế theo cách này?
Deduplicator

5
"Bởi vì nó không cần" - nó cũng có thể được thiết kế để nó cần.
dùng253751

5
@Mehrdad rằng một sự tương tự hoàn toàn không rõ ràng và bị lỗi. Nếu bạn phân bổ 4 byte trong một trăm thời gian, điều đó chắc chắn là quan trọng nhất chính xác bạn đang giải phóng cái nào. giải phóng cái đầu tiên không giống như giải phóng cái thứ hai. với số tiền mặt khác nó không quan trọng nếu bạn trả đầu tiên hoặc mượn thứ hai đầu tiên, nó chỉ là một đống lớn
user2520938

14

C có thể không "trừu tượng" như C ++, nhưng nó vẫn được dự định là một sự trừu tượng so với hợp ngữ. Do đó, các chi tiết cấp thấp nhất được đưa ra khỏi phương trình. Điều này giúp bạn không cần phải căn chỉnh và đệm phần lớn, điều này sẽ làm cho tất cả các chương trình C của bạn không di động được.

Tóm lại, đây là toàn bộ điểm của việc viết một bài trừu tượng .


9
Không chắc chắn những gì căn chỉnh hoặc đệm liên quan đến điều này. Câu trả lời không thực sự trả lời bất cứ điều gì.
user541686

1
@Mehrdad C không phải là một ngôn ngữ x86, nó cố gắng (ít nhiều) để có thể di động và do đó giải phóng cho lập trình viên gánh nặng đáng kể đó. Bạn có thể đạt đến mức đó bằng nhiều cách khác nhau (ví dụ như lắp ráp nội tuyến) nhưng trừu tượng là chìa khóa. Tôi đồng ý với câu trả lời này.
Marco A.

4
@Mehrdad: Nếu bạn yêu cầu mallocN byte và thay vào đó, nó trả về một con trỏ về đầu toàn bộ trang (do căn chỉnh, đệm hoặc các ràng buộc khác , sẽ không có cách nào để người dùng theo dõi nó - buộc họ phải làm nó sẽ phản tác dụng.
Michael Foukarakis

3
@MichaelFoukarakis: malloccó thể đơn giản luôn trả về một con trỏ được căn chỉnh mà không cần lưu trữ kích thước của phân bổ. freesau đó có thể làm tròn đến căn chỉnh thích hợp để đảm bảo mọi thứ được giải phóng đúng cách. Tôi không thấy vấn đề ở đâu.
user541686

6
@Mehrdad: Không có lợi gì cho tất cả những việc làm thêm mà bạn vừa đề cập. Ngoài ra, việc chuyển một sizetham số để freemở ra một nguồn lỗi khác.
Michael Foukarakis

14

Trên thực tế, trong trình cấp phát bộ nhớ hạt nhân Unix cổ xưa, mfree()đã có một sizecuộc tranh cãi. malloc()mfree()giữ hai mảng (một mảng cho bộ nhớ lõi, một mảng khác để hoán đổi) chứa thông tin về địa chỉ và kích thước khối trống.

Không có trình phân bổ không gian người dùng cho đến Unix V6 (các chương trình sẽ chỉ sử dụng sbrk()). Trong Unix V6, iolib bao gồm một bộ cấp phát với alloc(size)và một free()lệnh gọi không nhận đối số kích thước. Mỗi khối bộ nhớ được đặt trước kích thước của nó và một con trỏ tới khối tiếp theo. Con trỏ chỉ được sử dụng trên các khối miễn phí, khi xem danh sách miễn phí và được sử dụng lại làm bộ nhớ khối trên các khối đang sử dụng.

Trong Unix 32V và Unix V7, điều này được thay thế bằng một triển khai malloc()và mới free(), free()không có sizetranh cãi. Việc triển khai là một danh sách vòng tròn, mỗi đoạn được đặt trước một từ chứa một con trỏ đến đoạn tiếp theo và một bit "bận" (được cấp phát). Vì vậy, malloc()/free()thậm chí không theo dõi kích thước rõ ràng.


10

Tại sao freetrong C không lấy số byte được giải phóng?

Bởi vì nó không cần thiết. Thông tin đã có sẵn trong quản lý nội bộ do malloc / free thực hiện.

Dưới đây là hai cân nhắc (có thể có hoặc không góp phần vào quyết định này):

  • Tại sao bạn lại mong đợi một hàm nhận một tham số mà nó không cần?

    (điều này sẽ làm phức tạp hầu như tất cả mã máy khách dựa vào bộ nhớ động và thêm phần dư thừa hoàn toàn không cần thiết vào ứng dụng của bạn). Theo dõi phân bổ con trỏ đã là một vấn đề khó khăn. Theo dõi phân bổ bộ nhớ cùng với các kích thước liên quan sẽ làm tăng độ phức tạp của mã máy khách một cách không cần thiết.

  • freeTrong những trường hợp này, chức năng được thay đổi sẽ làm gì?

    void * p = malloc(20);
    free(p, 25); // (1) wrong size provided by client code
    free(NULL, 10); // (2) generic argument mismatch
    

    Nó sẽ không miễn phí (gây ra rò rỉ bộ nhớ?)? Bỏ qua tham số thứ hai? Dừng ứng dụng bằng cách gọi thoát? Việc triển khai điều này sẽ thêm điểm lỗi trong ứng dụng của bạn, đối với một tính năng mà bạn có thể không cần (và nếu bạn cần, hãy xem điểm cuối cùng của tôi, bên dưới - "giải pháp triển khai ở cấp ứng dụng").

Đúng hơn, tôi muốn biết tại sao miễn phí lại được tạo ra theo cách này ngay từ đầu.

Vì đây là cách làm “đúng đắn”. Một API nên yêu cầu các đối số mà nó cần để thực hiện hoạt động của nó và không hơn thế nữa .

Tôi cũng xảy ra với tôi rằng việc cung cấp số lượng byte miễn phí một cách rõ ràng có thể cho phép một số tối ưu hóa hiệu suất, ví dụ: một trình phân bổ có các nhóm riêng biệt cho các kích thước phân bổ khác nhau sẽ có thể xác định nhóm nào cần giải phóng chỉ bằng cách xem các đối số đầu vào, và tổng thể sẽ có ít không gian hơn.

Các cách thích hợp để thực hiện điều đó, là:

  • (ở cấp hệ thống) trong quá trình triển khai malloc - không có gì ngăn người triển khai thư viện viết malloc để sử dụng các chiến lược khác nhau trong nội bộ, dựa trên kích thước nhận được.

  • (ở cấp ứng dụng) bằng cách gói malloc và miễn phí trong các API của riêng bạn và sử dụng chúng thay thế (ở mọi nơi trong ứng dụng của bạn mà bạn có thể cần).


6
@ user3477950: Nó không cần phải vượt qua số byte : Vâng, vì nó được thiết kế theo cách này. OP hỏi Tại sao nó được thiết kế theo cách này?
Deduplicator

4
"Bởi vì nó không cần" - nó cũng có thể được thiết kế để nó cần, bằng cách không lưu thông tin đó.
dùng253751

1
Đối với điểm 2 của bạn, bạn có tôi tự hỏi nếu miễn phí (NULL) là hành vi được định nghĩa. Aha, "Tất cả các phiên bản tuân thủ tiêu chuẩn của thư viện C đều coi miễn phí (NULL) là không cần tham gia" - nguồn stackoverflow.com/questions/1938735/…
Mawg cho biết hãy khôi phục Monica

10

Năm lý do khiến bạn nhớ đến:

  1. Thật tiện lợi. Nó loại bỏ toàn bộ tải trọng từ lập trình viên và tránh một lớp lỗi cực kỳ khó theo dõi.

  2. Nó mở ra khả năng giải phóng một phần của khối. Nhưng vì các nhà quản lý bộ nhớ thường muốn có thông tin theo dõi nên không rõ điều này có nghĩa là gì?

  3. Lightness Races In Orbit được chú ý về phần đệm và căn chỉnh. Bản chất của quản lý bộ nhớ có nghĩa là kích thước thực tế được phân bổ có thể khác với kích thước bạn yêu cầu. Điều này có nghĩa là freeyêu cầu kích thước cũng như vị trí mallocsẽ phải được thay đổi để trả về kích thước thực tế được phân bổ.

  4. Dù sao thì vẫn chưa rõ rằng có lợi ích thực sự nào khi vượt qua kích thước. Một trình quản lý bộ nhớ điển hình có 4-16 byte tiêu đề cho mỗi đoạn bộ nhớ, bao gồm cả kích thước. Tiêu đề phân đoạn này có thể phổ biến cho bộ nhớ được cấp phát và chưa phân bổ và khi các phần liền kề được giải phóng, chúng có thể được thu gọn lại với nhau. Nếu bạn đang yêu cầu người gọi lưu trữ bộ nhớ trống, bạn có thể giải phóng có thể 4 byte cho mỗi đoạn bằng cách không có trường kích thước riêng biệt trong bộ nhớ được cấp phát nhưng trường kích thước đó có thể không đạt được vì người gọi cần lưu trữ nó ở đâu đó. Nhưng bây giờ thông tin đó nằm rải rác trong bộ nhớ thay vì nằm trong phần tiêu đề có khả năng hoạt động kém hiệu quả hơn.

  5. Thậm chí nếu nó hiệu quả hơn nó triệt để chắc chương trình của bạn đang dành một lượng lớn thời gian bộ nhớ giải phóng nào để lợi ích sẽ là rất nhỏ.

Ngẫu nhiên, ý tưởng của bạn về các bộ phân bổ riêng biệt cho các mục có kích thước khác nhau dễ dàng được thực hiện mà không cần thông tin này (bạn có thể sử dụng địa chỉ để xác định nơi xảy ra phân bổ). Điều này được thực hiện thường xuyên trong C ++.

Được thêm sau

Một câu trả lời khác, khá là nực cười, đã đưa ra std :: Distribator làm bằng chứng freecó thể hoạt động theo cách này nhưng trên thực tế, nó là một ví dụ điển hình về lý do tại sao freekhông hoạt động theo cách này. Có hai điểm khác biệt chính giữa cái malloc/ freelàm và cái mà std :: phân bổ làm. Thứ nhất, mallocfreelà người dùng phải đối mặt - chúng được thiết kế cho các lập trình viên nói chung làm việc - trong khi std::allocatorđược thiết kế để cung cấp phân bổ bộ nhớ chuyên dụng cho thư viện tiêu chuẩn. Điều này cung cấp một ví dụ hay về thời điểm điểm đầu tiên của tôi không hoặc không quan trọng. Vì nó là một thư viện, nên dù sao đi nữa, những khó khăn trong việc xử lý sự phức tạp của kích thước theo dõi cũng được ẩn khỏi người dùng.

Thứ hai, std :: certator luôn hoạt động với cùng một mục kích thước, điều này có nghĩa là nó có thể sử dụng số lượng phần tử được truyền ban đầu để xác định bao nhiêu phần tử còn trống. Tại sao điều này khác với freechính nó là minh họa. Trong std::allocatorcác hạng mục được phân bổ luôn có cùng loại, đã biết, kích thước và luôn luôn cùng loại nên chúng luôn có cùng một loại yêu cầu về căn chỉnh. Điều này có nghĩa là trình cấp phát có thể được chuyên môn hóa để chỉ cần cấp phát một mảng các mục này ngay từ đầu và loại bỏ chúng khi cần thiết. Bạn không thể làm điều này với freevì không có cách nào để đảm bảo rằng kích thước tốt nhất để trở lại là kích thước yêu cầu, thay vào đó là hiệu quả hơn để thỉnh thoảng trở lại khối lớn hơn người gọi yêu cầu * và do đó một trong haingười dùng hoặc người quản lý cần theo dõi kích thước chính xác thực sự được cấp. Chuyển những loại chi tiết triển khai này cho người dùng là một vấn đề nhức đầu không cần thiết mà không mang lại lợi ích gì cho người gọi.

- * Nếu ai đó vẫn còn khó hiểu về điểm này, hãy xem xét điều này: một trình cấp phát bộ nhớ thông thường thêm một lượng nhỏ thông tin theo dõi vào phần bắt đầu của khối bộ nhớ và sau đó trả về độ lệch con trỏ từ đó. Thông tin được lưu trữ ở đây thường bao gồm các con trỏ đến khối miễn phí tiếp theo, chẳng hạn. Giả sử tiêu đề đó chỉ dài 4 byte (thực sự nhỏ hơn hầu hết các thư viện thực) và không bao gồm kích thước, sau đó hãy tưởng tượng chúng ta có một khối trống 20 byte khi người dùng yêu cầu một khối 16 byte, thật ngây thơ hệ thống sẽ trả về khối 16byte nhưng sau đó để lại một đoạn 4byte mà không bao giờ có thể được sử dụng lãng phí thời gian mỗi lầnmallocđược gọi. Nếu thay vào đó, trình quản lý chỉ trả về khối 20 byte thì nó sẽ lưu các phân đoạn lộn xộn này khỏi xây dựng và có thể phân bổ bộ nhớ khả dụng một cách rõ ràng hơn. Nhưng nếu hệ thống thực hiện điều này một cách chính xác mà không theo dõi kích thước của chính nó thì chúng tôi yêu cầu người dùng theo dõi - đối với mọi phân bổ đơn lẻ - lượng bộ nhớ thực sự được cấp phát nếu muốn chuyển nó trở lại miễn phí. Đối số tương tự áp dụng cho phần đệm cho các loại / phân bổ không khớp với ranh giới mong muốn. Vì vậy, tối đa, việc yêu cầu freelấy một kích thước là (a) hoàn toàn vô dụng vì trình cấp phát bộ nhớ không thể dựa vào kích thước đã truyền để khớp với kích thước thực được cấp phát hoặc (b) vô nghĩa yêu cầu người dùng thực hiện công việc theo dõi thực kích thước sẽ dễ dàng được xử lý bởi bất kỳ trình quản lý bộ nhớ hợp lý nào.


# 1 là đúng. # 2: Không chắc ý bạn. Không chắc lắm về # 3, căn chỉnh không yêu cầu lưu trữ thêm thông tin. # 4 là lý luận vòng tròn; trình quản lý bộ nhớ chỉ yêu cầu chi phí đó cho mỗi đoạn chúng lưu trữ kích thước, vì vậy bạn không thể sử dụng điều đó làm đối số cho lý do tại sao chúng lưu trữ kích thước. Và # 5 rất đáng tranh luận.
user541686

Tôi chấp nhận và sau đó không chấp nhận - đây có vẻ như là một câu trả lời hay nhưng câu hỏi có vẻ như đang có nhiều hoạt động, tôi nghĩ rằng tôi có thể đã hơi sớm. Điều này chắc chắn cho tôi một cái gì đó để suy nghĩ lại.
jaymmer - Khôi phục Monica

2
@jaymmer: Ừ, còn quá sớm. Tôi khuyên bạn nên đợi hơn một hoặc hai ngày trước khi chấp nhận, và tôi khuyên bạn nên tự mình suy nghĩ về điều đó. Đó là một câu hỏi thực sự thú vị và hầu hết / tất cả các câu trả lời ban đầu bạn sẽ nhận được cho bất kỳ câu hỏi "tại sao" như vậy trên StackOverflow sẽ chỉ là những nỗ lực bán tự động để biện minh cho hệ thống hiện tại thay vì thực sự giải quyết câu hỏi cơ bản.
user541686

@Mehrdad: Bạn đã hiểu sai những gì tôi đang nói trong # 4. Tôi không nói rằng nó sẽ có thêm kích thước, tôi đang nói (a) nó sẽ di chuyển ai phải lưu trữ kích thước vì vậy nó sẽ không thực sự tiết kiệm bất kỳ không gian nào và (b) thay đổi kết quả thực sự có khả năng làm cho nó kém hiệu quả hơn không nhiều hơn. Đối với # 5, tôi không tin rằng nó có thể gây tranh cãi chút nào: chúng tôi - nhiều nhất - đang nói về việc lưu một vài hướng dẫn từ cuộc gọi miễn phí. So với chi phí của cuộc gọi miễn phí sẽ rất nhỏ.
Jack Aidley

1
@Mehrdad: Ồ, và ở # 3, không, nó không yêu cầu thêm thông tin, nó cần thêm bộ nhớ. Một trình quản lý bộ nhớ điển hình được thiết kế để hoạt động với sự liên kết 16 byte sẽ trả về một con trỏ đến khối 128 byte khi được yêu cầu khối 115 byte. Nếu freecuộc gọi là vượt qua chính xác kích thước được giải phóng, nó phải biết điều này.
Jack Aidley

5

Tôi chỉ đăng bài này như một câu trả lời không phải vì đó là câu bạn đang hy vọng mà vì tôi tin rằng đó là câu trả lời chính xác duy nhất:

Có lẽ ban đầu nó được coi là thuận tiện, và sau đó nó không thể được cải thiện.
Có thể không có lý do thuyết phục cho nó. (Nhưng tôi sẽ vui vẻ xóa nó nếu nó không chính xác.)

sẽ có những lợi ích nếu nó đã có thể: bạn có thể phân bổ một mảnh lớn duy nhất của bộ nhớ có kích thước mà bạn biết trước, sau đó giải phóng một chút tại một thời điểm - như trái ngược với nhiều lần phân bổ và giải phóng khối nhỏ của bộ nhớ. Hiện tại các nhiệm vụ như thế này không thể thực hiện được.


Đối với nhiều (nhiều 1 !) Trong số các bạn nghĩ rằng việc vượt qua kích thước là quá vô lý:

Tôi có thể giới thiệu cho bạn quyết định thiết kế của C ++ cho std::allocator<T>::deallocatephương pháp không?

void deallocate(pointer p, size_type n);

Tất cả các đối tượng trong khu vực được trỏ đến sẽ bị phá hủy trước lệnh gọi này. phải khớp với giá trị được truyền để có được bộ nhớ này.n Tp
nallocate

Tôi nghĩ bạn sẽ có một khoảng thời gian khá "thú vị" khi phân tích quyết định thiết kế này.


Về phần operator delete, hóa ra đề xuất N3778 2013 (" Phân bổ kích thước C ++") cũng nhằm khắc phục điều đó.


1 Chỉ cần nhìn vào các bình luận dưới câu hỏi ban đầu để xem có bao nhiêu người đã khẳng định vội vàng như "kích thước được yêu cầu là hoàn toàn vô dụng cho freecuộc gọi" để biện minh cho việc thiếu sizetham số.


5
Để malloc()thực hiện, nó cũng sẽ loại bỏ sự cần thiết phải nhớ bất cứ điều gì về một khu vực được phân bổ, giảm chi phí phân bổ cho tổng chi phí liên kết. malloc()sẽ có thể tự thực hiện tất cả việc ghi sổ trong các phần được giải phóng. Có những trường hợp sử dụng trong đó đây sẽ là một cải tiến lớn. Tuy nhiên, việc phân bổ một lượng lớn bộ nhớ thành nhiều phần nhỏ sẽ không được khuyến khích, tuy nhiên, điều này sẽ làm tăng đáng kể sự phân mảnh.
cmaster - Khôi phục monica

1
@cmaster: Những trường hợp sử dụng đó chính xác là loại mà tôi đang đề cập đến, bạn đã đánh đinh vào đầu, cảm ơn. Liên quan đến phân mảnh: không chắc chắn làm thế nào điều đó làm tăng phân mảnh so với giải pháp thay thế, đó là cả cấp phát và giải phóng bộ nhớ trong các phần nhỏ.
user541686

std::allocatorchỉ phân bổ các phần tử kích thước cụ thể, đã biết. Nó không phải là một công cụ phân bổ mục đích chung, so sánh là táo với cam.
Jack Aidley

Đối với tôi, dường như một quyết định thiết kế một phần triết học, một phần đã được thực hiện để làm cho thư viện tiêu chuẩn trong C trở thành một tập hợp tối thiểu các nguyên thủy mà từ đó hầu như mọi thứ đều có thể được xây dựng - ban đầu được dự định là một ngôn ngữ cấp hệ thống và có thể di chuyển đến các hệ thống mà cách tiếp cận sơ khai này làm cho giác quan. Với C ++, một quyết định khác đã được đưa ra để làm cho thư viện tiêu chuẩn trở nên rất rộng rãi (và ngày càng lớn hơn với C ++ 11). Phần cứng phát triển nhanh hơn, bộ nhớ lớn hơn, kiến ​​trúc phức tạp hơn và nhu cầu giải quyết sự phát triển ứng dụng cấp cao hơn có lẽ góp phần vào sự thay đổi của sự nhấn mạnh này.
Clifford

1
@Clifford: Chính xác - đó là lý do tại sao tôi nói rằng không có lý do thuyết phục nào cho điều đó. Đó chỉ là một quyết định đã được đưa ra, và không có lý do gì để tin rằng nó hoàn toàn tốt hơn các lựa chọn thay thế.
user541686

2

malloc và free đi đôi với nhau, với mỗi "malloc" được khớp với một "free". Do đó, hoàn toàn có ý nghĩa rằng "miễn phí" khớp với "malloc" trước đó sẽ chỉ đơn giản là giải phóng lượng bộ nhớ được phân bổ bởi malloc đó - đây là trường hợp sử dụng đa số có ý nghĩa trong 99% trường hợp. Hãy tưởng tượng tất cả các lỗi bộ nhớ nếu tất cả các lập trình viên trên toàn thế giới sử dụng malloc / free, sẽ cần lập trình viên theo dõi số lượng được phân bổ trong malloc, và sau đó nhớ giải phóng tương tự. Kịch bản bạn nói về thực sự nên sử dụng nhiều mallocs / frees trong một số loại triển khai quản lý bộ nhớ.


5
Tôi nghĩ rằng "Hãy tưởng tượng tất cả các lỗi [...]" là một cuộc tranh luận khi bạn nghĩ về các nhà máy sản xuất lỗi khác như gets, printfvòng lặp thủ công (từng cái một), hành vi không xác định, chuỗi định dạng, chuyển đổi ngầm, thủ thuật bit, v.v. thiên thạch.
Sebastian Mach

1

Tôi muốn đề xuất rằng đó là bởi vì nó rất thuận tiện khi không phải theo dõi thông tin kích thước theo cách thủ công theo cách này (trong một số trường hợp) và cũng ít bị lỗi lập trình viên hơn.

Ngoài ra, realloc sẽ cần thông tin kế toán này, mà tôi mong đợi chứa nhiều hơn là chỉ kích thước phân bổ. tức là nó cho phép xác định cơ chế hoạt động của nó.

Bạn có thể viết trình cấp phát của riêng mình hoạt động phần nào theo cách bạn đề xuất và nó thường được thực hiện trong c ++ cho trình phân bổ nhóm theo một cách tương tự cho các trường hợp cụ thể (có khả năng tăng hiệu suất lớn) mặc dù điều này thường được triển khai về mặt toán tử mới để phân bổ khối hồ bơi.


1

Tôi không biết trình phân bổ sẽ hoạt động như thế nào mà không theo dõi kích thước của các phân bổ của nó. Nếu nó không làm điều này, làm thế nào nó biết được bộ nhớ nào có sẵn để đáp ứng mallocyêu cầu trong tương lai ? Nó ít nhất phải lưu trữ một số loại cấu trúc dữ liệu có chứa địa chỉ và độ dài, để chỉ ra vị trí của các khối bộ nhớ khả dụng. (Và tất nhiên, việc lưu trữ một danh sách các không gian trống tương đương với việc lưu trữ một danh sách các không gian được cấp phát).


Nó thậm chí không cần một trường kích thước rõ ràng. Nó chỉ có thể có một con trỏ đến khối tiếp theo và một bit được phân bổ.
ninjalj

0

Chà, thứ duy nhất bạn cần là một con trỏ mà bạn sẽ sử dụng để giải phóng bộ nhớ mà bạn đã cấp phát trước đó. Số lượng byte là thứ do hệ điều hành quản lý nên bạn không phải lo lắng về nó. Không cần thiết phải lấy số byte được cấp phát bởi free (). Tôi đề nghị bạn một cách thủ công để đếm số byte / vị trí được phân bổ bởi một chương trình đang chạy:

Nếu bạn làm việc trong Linux và bạn muốn biết số lượng byte / vị trí mà malloc đã phân bổ, bạn có thể tạo một chương trình đơn giản sử dụng malloc một lần hoặc n lần và in ra các con trỏ bạn nhận được. Ngoài ra, bạn phải đặt chương trình ở chế độ ngủ trong vài giây (đủ để bạn làm những việc sau). Sau đó, chạy chương trình đó, tìm PID của nó, viết cd / proc / process_PID và chỉ cần nhập "cat maps". Kết quả đầu ra sẽ hiển thị cho bạn, trong một dòng cụ thể, cả địa chỉ bộ nhớ đầu và cuối của vùng bộ nhớ heap (vùng mà bạn đang cấp phát bộ nhớ di động). Nếu bạn in ra các con trỏ đến các vùng bộ nhớ này đang được cấp phát, bạn có thể đoán bạn đã phân bổ bao nhiêu bộ nhớ.

Hy vọng nó giúp!


0

Tại sao nên làm thế? malloc () và free () là những nguyên thủy quản lý bộ nhớ rất đơn giản có chủ đích và việc quản lý bộ nhớ cấp cao hơn trong C phần lớn phụ thuộc vào nhà phát triển. T

Hơn nữa realloc () đã làm được điều đó - nếu bạn giảm phân bổ trong realloc () thì nó sẽ không di chuyển dữ liệu và con trỏ trả về sẽ giống như con trỏ ban đầu.

Nói chung đúng với toàn bộ thư viện tiêu chuẩn rằng nó bao gồm các nguyên thủy đơn giản mà từ đó bạn có thể xây dựng các hàm phức tạp hơn để phù hợp với nhu cầu ứng dụng của mình. Vì vậy, câu trả lời cho bất kỳ câu hỏi nào của dạng "tại sao thư viện chuẩn không làm được X" là vì nó không thể làm mọi thứ mà một lập trình viên có thể nghĩ ra (đó là những gì lập trình viên dành cho), vì vậy nó chọn làm rất ít - xây dựng của riêng bạn hoặc sử dụng thư viện của bên thứ ba. Nếu bạn muốn có một thư viện tiêu chuẩn mở rộng hơn - bao gồm quản lý bộ nhớ linh hoạt hơn, thì C ++ có thể là câu trả lời.

Bạn đã gắn thẻ câu hỏi C ++ cũng như C và nếu C ++ là những gì bạn đang sử dụng, thì bạn hầu như không nên sử dụng malloc / free trong mọi trường hợp - ngoài new / delete, các lớp vùng chứa STL quản lý bộ nhớ tự động và theo cách có thể để phù hợp cụ thể với bản chất của các thùng chứa khác nhau.


Hơn nữa realloc () đã làm được điều đó - nếu bạn giảm phân bổ trong realloc () thì nó sẽ không di chuyển dữ liệu và con trỏ trả về sẽ giống như con trỏ ban đầu. Đây có phải là hành vi được đảm bảo? Nó dường như phổ biến trong một số trình phân bổ nhúng, nhưng tôi không chắc liệu hành vi này có được chỉ định như một phần của tiêu chuẩn-c hay không.
rsaxvc

@rsaxvc: Câu hỏi hay - cplusplus.com tài liệu "Nếu kích thước mới lớn hơn, giá trị của phần mới được phân bổ là không xác định." , ngụ ý rằng nếu nhỏ hơn thì nó là xác định . [opengroup.org () nói "Nếu kích thước mới của đối tượng bộ nhớ yêu cầu chuyển động của đối tượng, thì không gian cho việc khởi tạo trước đó của đối tượng sẽ được giải phóng." - nếu nó nhỏ hơn, dữ liệu sẽ không cần phải di chuyển. Một lần nữa, hàm ý là nhỏ hơn sẽ không phân bổ lại. Tôi không chắc tiêu chuẩn ISO nói gì.
Clifford

1
reallochoàn toàn được phép di chuyển dữ liệu. Theo tiêu chuẩn C, hoàn toàn hợp pháp để thực hiện reallocdưới dạng malloc+ memcpy+ free. Và có những lý do chính đáng tại sao một triển khai có thể muốn di chuyển một phân bổ đã được giảm kích thước, ví dụ như để tránh phân mảnh bộ nhớ.
Nathaniel J. Smith
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.