2011-10-30 57 views
17

Tôi đọc rằng trên hệ thống Unix, malloc có thể trả về con trỏ không NULL ngay cả khi bộ nhớ không thực sự khả dụng và cố gắng sử dụng bộ nhớ sau này sẽ kích hoạt lỗi. Vì tôi không thể bắt lỗi như vậy bằng cách kiểm tra NULL, tôi tự hỏi làm thế nào hữu ích nó là để kiểm tra NULL ở tất cả?Tôi có thể dựa vào NULL trở về malloc không?

Trên một lưu ý liên quan, Herb Sutter nói rằng việc xử lý lỗi bộ nhớ C++ là vô ích, bởi vì hệ thống sẽ đi vào sự phân trang dài trước khi một ngoại lệ thực sự xảy ra. Điều này cũng áp dụng cho malloc không?

+3

Tôi nghĩ rằng bạn không nên sử dụng malloc trong C++: http://stackoverflow.com/questions/184537/in-what-cases-do-i-use-malloc-vs-new – lc2817

+1

@ lc2817 bạn chỉ nên sử dụng malloc nếu bạn đang viết mã với giao diện C (tức là các hàm được sử dụng từ C nhưng được viết bằng C++) ** và ** mã C chịu trách nhiệm giải phóng bộ nhớ đó. –

+0

@WTP cảm ơn độ chính xác này. Mặc dù, tôi không biết nếu đó là trường hợp ở đây. – lc2817

Trả lời

32

Trích dẫn Linux manuals:

Theo mặc định, Linux sau một chiến lược phân bổ bộ nhớ lạc quan. Điều này có nghĩa là khi malloc() trả về không NULL thì không có đảm bảo rằng bộ nhớ thực sự khả dụng. Đây là một lỗi thực sự tồi tệ. Trong trường hợp hệ thống đã hết bộ nhớ, một hoặc nhiều quy trình sẽ bị giết bởi kẻ giết người OOM khét tiếng. Trong trường hợp Linux được sử dụng trong những trường hợp mà nó sẽ ít mong muốn đột nhiên mất một số ngẫu nhiên chọn quy trình, và hơn nữa phiên bản hạt nhân là đủ gần đây, người ta có thể tắt hành vi overcommitting này sử dụng một lệnh như:

# echo 2 > /proc/sys/vm/overcommit_memory 

Bạn nên kiểm tra trả lại NULL, đặc biệt là trên hệ thống 32 bit, vì không gian địa chỉ quy trình có thể cạn kiệt trước RAM: trên Linux 32 bit, quy trình người dùng có thể có dung lượng địa chỉ có thể sử dụng là 2G - 3G như trái ngược với hơn 4G của tổng số RAM. Trên các hệ thống 64 bit, có thể vô ích khi kiểm tra mã trả về malloc, nhưng có thể được coi là thực hành tốt, và nó làm cho chương trình của bạn dễ dàng hơn. Và, hãy nhớ, dereferencing con trỏ null giết chết quá trình của bạn chắc chắn; một số trao đổi có thể không làm tổn thương nhiều so với điều đó.

Nếu malloc xảy ra để trở NULL khi một cố gắng phân bổ chỉ một lượng nhỏ bộ nhớ, sau đó người ta phải thận trọng khi cố gắng để phục hồi từ tình trạng lỗi như bất kỳ tiếp theo malloc có thể thất bại quá, cho đến khi đủ bộ nhớ có sẵn.

Toán tử C++ mặc định new thường là trình bao bọc trên cùng một cơ chế phân bổ được sử dụng bởi malloc().

+8

+1 để trích dẫn một ý thích hợp lý về cách mặc định của Linux bị hỏng **. Một chương trình tốt nên luôn kiểm tra giá trị trả về của 'malloc'. Nếu người dùng đã định cấu hình sai hệ thống của họ (hoặc để lại cấu hình mặc định bị hỏng), thì tất nhiên điều này có thể không hữu ích, nhưng bạn không thể làm gì và sự cố nằm ngoài trách nhiệm của bạn. Nhưng nếu bạn không kiểm tra giá trị trả về của 'malloc', chương trình của bạn sẽ bị ngắt ngay cả khi chạy trên các hệ thống mà người dùng/quản trị viên ** thực sự quan tâm đến tính chính xác ** và đã vô hiệu hóa quá mức. Người dùng sau đó có thể sẽ xem xét chương trình crap của bạn. :-) –

+2

Vâng, sự thật phức tạp hơn một chút. Có lỗ hổng trong không gian địa chỉ quy trình; ví dụ: chương trình có thể không bao giờ chạm vào tất cả các trang trong BSS hoặc thay đổi trang được ánh xạ trong Phân đoạn dữ liệu. Một thiếu sót thường là một vấn đề lớn hơn trên một hệ thống máy tính để bàn/máy chủ hơn là overcommit. Và phân vùng trao đổi, nếu được bật, cũng cung cấp một số đệm trước khi mọi thứ trở nên tồi tệ. –

+0

Tôi không đồng ý. Undercommit không phải là một vấn đề bởi vì bạn luôn có thể ném thêm hoán đổi vào nó. Trong bất kỳ trường hợp nào nếu bạn có các trang bss/data bị ảnh hưởng, điều đó có nghĩa là bạn có các biến toàn cầu (không chỉ GOT/PLT ở đó) mà là một vấn đề lớn hơn. :-) Có lẽ một vài là cần thiết, nhưng nhiều hơn một trang hoặc hai giá trị gần như chắc chắn là dấu hiệu của vấn đề thiết kế ... –

5

Trên Linux, bạn có thể thực sự không dựa vào malloc trở NULL nếu đủ bộ nhớ là không có sẵn do chiến lược overallocation của hạt nhân, nhưng bạn vẫn nên kiểm tra cho nó bởi vì trong một số trường hợp mallocsẽ trở NULL, ví dụ khi bạn yêu cầu nhiều bộ nhớ hơn là có sẵn trong tổng số máy. Linux malloc(3) manpage gọi tổng thể là "một lỗi thực sự xấu" và có lời khuyên về cách tắt nó đi.

Tôi chưa bao giờ nghe nói về hành vi này cũng xảy ra trong các biến thể Unix khác.

Đối với "co thắt phân trang", điều đó phụ thuộc vào thiết lập máy. Ví dụ: tôi có xu hướng không thiết lập phân vùng trao đổi trên cài đặt Linux cho máy tính xách tay, vì hành vi chính xác mà bạn sợ có thể làm hỏng đĩa cứng. Tôi vẫn muốn các chương trình C/C++ mà tôi chạy để kiểm tra các giá trị trả về malloc, cung cấp thông báo lỗi thích hợp và khi có thể làm sạch sau khi chúng.

+1

Ghi đè không phải là một tính năng cũng như một lỗi, nói đúng. Đó chỉ là sự lười biếng lịch sử: thừa nhận là rất nhiều * dễ dàng hơn * để thực hiện hơn là tính phí cam kết. Có lẽ một số người đã quen với nó và thích nó (vì bất kỳ lý do gì) và một số thậm chí bắt đầu viết các chương trình mà 'malloc' 1gb thành một mảng thưa thớt và thậm chí nhiều thứ hư hỏng hơn, vì vậy bây giờ chúng ta đang mắc kẹt với nó mặc định ... –

1

Để xem này từ một điểm khác để xem:

"malloc có thể trả về một con trỏ không NULL ngay cả khi bộ nhớ là không thực sự sẵn" không có nghĩa là nó luôn luôn trả về không NULL. Có thể (và sẽ) là trường hợp NULL được trả về (như những người khác đã nói), vì vậy kiểm tra này là cần thiết tuy nhiên.

2

Kiểm tra sự trở lại của malloc không giúp bạn nhiều bởi chính nó để làm cho phân bổ của bạn an toàn hơn hoặc ít bị lỗi hơn. Nó thậm chí có thể là một cái bẫy nếu đây là thử nghiệm duy nhất mà bạn thực hiện.

Khi được gọi với đối số là 0, tiêu chuẩn cho phép malloc trả về một loại địa chỉ duy nhất, không phải là con trỏ rỗng và bạn không có quyền truy cập. Vì vậy, nếu bạn chỉ kiểm tra xem sự trở lại là 0 nhưng không kiểm tra các đối số cho malloc, calloc hoặc realloc bạn có thể gặp phải một sự xâm nhập sau này.

Tình trạng lỗi này (bộ nhớ cạn kiệt) khá hiếm trong môi trường "được lưu trữ". Thông thường bạn đang gặp rắc rối lâu trước khi bạn gặp rắc rối với loại lỗi này. (Nhưng nếu bạn đang viết thư viện thời gian chạy, là một hacker hạt nhân hoặc tên lửa xây dựng này là khác nhau, và có các thử nghiệm làm cho cảm giác hoàn hảo.)

Mọi người sau đó có xu hướng trang trí mã của họ với điều kiện lỗi phức tạp mà span dòng, làm perror và các nội dung tương tự, có thể ảnh hưởng đến khả năng đọc của mã.

Tôi nghĩ rằng điều này "kiểm tra sự trở lại của malloc" được đánh giá quá cao, đôi khi thậm chí còn bảo vệ khá một cách giáo điều. Những thứ khác quan trọng hơn nhiều:

  • luôn khởi tạo biến luôn. đối với các biến con trỏ, điều này rất quan trọng, để chương trình gặp sự cố độc đáo trước khi mọi thứ trở nên quá tệ. thành viên con trỏ chưa được khởi tạo trong struct s là nguyên nhân quan trọng của các lỗi khó tìm.
  • luôn kiểm tra đối số malloc và Co. nếu đây là biên dịch hằng số thời gian như sizof toto không có vấn đề gì, nhưng luôn đảm bảo phân bổ véc tơ của bạn xử lý đúng trường hợp 0.

Một điều dễ dàng để kiểm tra trả lại malloc là kết thúc bằng một cái gì đó như memset(malloc(n), 0, 1). Điều này chỉ viết một 0 trong byte đầu tiên và treo độc đáo nếu malloc gặp lỗi hoặc n0 để bắt đầu.

+0

Cho phép chỉ nói rằng nó là đẹp hơn để nói với người dùng "Out of heap tại dòng foo" hơn là chỉ "null con trỏ ngoại lệ tại quầy bar"; cho nó một wrapper đơn giản (macro?) cho malloc sẽ đủ. Điều này trong trường hợp một người sử dụng số lượng vô lý của bộ nhớ và có thể mong đợi sử dụng hơn 2G trên các hệ thống 32bit. –

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