Khi nào tôi nên sử dụng malloc trong C và khi nào thì không?


93

Tôi hiểu cách hoạt động của malloc (). Câu hỏi của tôi là, tôi sẽ thấy những thứ như thế này:

#define A_MEGABYTE (1024 * 1024)

char *some_memory;
size_t size_to_allocate = A_MEGABYTE;
some_memory = (char *)malloc(size_to_allocate);
sprintf(some_memory, "Hello World");
printf("%s\n", some_memory);
free(some_memory);

Tôi đã bỏ qua kiểm tra lỗi vì lý do ngắn gọn. Câu hỏi của tôi là, bạn không thể làm như trên bằng cách khởi tạo một con trỏ đến một số lưu trữ tĩnh trong bộ nhớ? có lẽ:

char *some_memory = "Hello World";

Tại thời điểm nào bạn thực sự cần tự cấp phát bộ nhớ thay vì khai báo / khởi tạo các giá trị bạn cần giữ lại?


5
Re: Tôi đã bỏ qua kiểm tra lỗi vì lý do ngắn gọn - tiếc là quá nhiều lập trình viên bỏ qua kiểm tra lỗi vì họ không nhận ra malloc()có thể thất bại!
Andrew

Câu trả lời:


131
char *some_memory = "Hello World";

đang tạo một con trỏ đến một hằng chuỗi. Điều đó có nghĩa là chuỗi "Hello World" sẽ ở đâu đó trong phần chỉ đọc của bộ nhớ và bạn chỉ có một con trỏ đến nó. Bạn có thể sử dụng chuỗi ở dạng chỉ đọc. Bạn không thể thay đổi nó. Thí dụ:

some_memory[0] = 'h';

Đang yêu cầu rắc rối.

Mặt khác

some_memory = (char *)malloc(size_to_allocate);

là cấp phát một mảng char (một biến) và some_memory trỏ đến bộ nhớ được cấp phát đó. Bây giờ mảng này là cả đọc và ghi. Bây giờ bạn có thể làm:

some_memory[0] = 'h';

và nội dung mảng thay đổi thành "hello World"


19
Chỉ cần làm rõ, nhiều như tôi thích câu trả lời này (tôi đã cho bạn +1), bạn có thể làm tương tự mà không cần malloc () bằng cách chỉ sử dụng một mảng ký tự. Một cái gì đó như: char some_memory [] = "Xin chào"; some_memory [0] = 'W'; cũng sẽ hoạt động.
randombits

18
Bạn đúng rồi. Bạn có thể làm điều đó. Khi bạn sử dụng malloc (), bộ nhớ được cấp phát động tại thời điểm chạy, vì vậy bạn không cần phải sửa kích thước mảng lúc biên dịch cũng như bạn có thể làm cho nó lớn lên hoặc thu nhỏ bằng cách sử dụng realloc () Bạn không thể thực hiện được những điều này: char some_memory [] = "Xin chào"; Ở đây, mặc dù bạn có thể thay đổi nội dung của mảng nhưng kích thước của nó là cố định. Vì vậy, tùy thuộc vào nhu cầu của bạn, bạn sử dụng một trong ba tùy chọn: 1) con trỏ tới char const 2) mảng được cấp phát động 3) kích thước cố định, biên dịch mảng được phân bổ thời gian.
codaddict

Để nhấn mạnh nó ở chế độ chỉ đọc, bạn nên viết const char *s = "hi";Điều này không thực sự bắt buộc theo tiêu chuẩn sao?
Till Theis

@Till, không vì bạn đã khai báo một con trỏ được khởi tạo cho địa chỉ cơ sở của chuỗi theo nghĩa đen "hi". s có thể được gán lại một cách hoàn hảo hợp pháp để trỏ đến một char không phải const. Nếu bạn muốn có một con trỏ hằng để đọc chỉ chuỗi, bạn cầnconst char const* s;
Rob11311

38

Đối với ví dụ chính xác đó, malloc ít được sử dụng.

Lý do chính mà malloc cần là khi bạn có dữ liệu phải có thời gian tồn tại khác với phạm vi mã. Mã của bạn gọi malloc theo một quy trình, lưu trữ con trỏ ở đâu đó và cuối cùng gọi miễn phí theo một quy trình khác.

Một lý do thứ hai là C không có cách nào để biết liệu có đủ không gian còn lại trên ngăn xếp để phân bổ hay không. Nếu mã của bạn cần phải mạnh mẽ 100%, sử dụng malloc sẽ an toàn hơn vì khi đó mã của bạn có thể biết phân bổ thất bại và xử lý nó.


4
Vòng đời của bộ nhớ và câu hỏi liên quan về thời điểm và cách phân bổ nó là một vấn đề quan trọng với nhiều thư viện và thành phần phần mềm phổ biến. Họ thường có một quy tắc cũng như các tài liệu: "Nếu bạn vượt qua một con trỏ đến này một trong những thói quen của tôi, bạn cần phải có malloc'd nó tôi sẽ theo dõi nó, và giải phóng nó khi tôi đang làm với nó.. " Một nguồn phổ biến của các lỗi khó chịu là chuyển một con trỏ đến bộ nhớ được cấp phát tĩnh cho một thư viện như vậy. Khi thư viện cố gắng giải phóng () nó, chương trình bị treo. Gần đây tôi đã dành rất nhiều thời gian để sửa một lỗi giống như lỗi mà người khác đã viết.
Bob Murphy

Có phải bạn đang nói rằng thời gian duy nhất mà malloc () được sử dụng thực tế, là khi có một đoạn mã sẽ được gọi nhiều lần trong vòng đời chương trình sẽ được gọi nhiều lần và cần được 'dọn sạch', vì malloc () có kèm theo miễn phí () không? Ví dụ, trong một trò chơi như bánh xe vận may, nơi sau khi bạn đoán và đặt đầu vào trong một mảng char được chỉ định, mảng có kích thước malloc () có thể được giải phóng cho lần đoán tiếp theo?
Smith sẽ đủ

Thời gian tồn tại của dữ liệu thực sự là lý do thực sự để sử dụng malloc. Sử dụng một kiểu dữ liệu trừu tượng được đại diện bởi một mô-đun, nó khai báo một kiểu Danh sách và các thủ tục để thêm / xóa các mục khỏi danh sách. Các giá trị mục đó, cần sao chép vào bộ nhớ được cấp phát động.
Rob11311

@Bob: những lỗi khó chịu đó, hãy quy ước rằng trình cấp phát giải phóng bộ nhớ vượt trội hơn nhiều, sau cùng thì bạn có thể đang tái chế nó. Giả sử bạn đã cấp phát bộ nhớ bằng calloc để cải thiện vị trí của các tham chiếu, điều này cho thấy bản chất bị hỏng của các thư viện đó, bởi vì bạn cần gọi miễn phí chỉ một lần cho toàn bộ khối. May mắn thay, tôi đã không phải sử dụng các thư viện chỉ định bộ nhớ là 'malloc-ed`, nó không phải là một truyền thống POSIX và rất có thể bị coi là một lỗi. Nếu họ "biết" bạn phải sử dụng malloc, tại sao thói quen thư viện không làm điều đó cho bạn?
Rob11311

17

malloc là một công cụ tuyệt vời để cấp phát, phân bổ lại và giải phóng bộ nhớ trong thời gian chạy, so với các khai báo tĩnh như ví dụ hello world của bạn, được xử lý tại thời điểm biên dịch và do đó không thể thay đổi kích thước.

Do đó, Malloc luôn hữu ích khi bạn xử lý dữ liệu có kích thước tùy ý, như đọc nội dung tệp hoặc xử lý các ổ cắm và bạn không biết về độ dài của dữ liệu để xử lý.

Tất nhiên, trong một ví dụ nhỏ nhặt như ví dụ bạn đưa ra, malloc không phải là "công cụ phù hợp cho đúng công việc" kỳ diệu, nhưng đối với những trường hợp phức tạp hơn (ví dụ: tạo một mảng có kích thước tùy ý trong thời gian chạy), đó là cách duy nhất để đi.


7

Nếu bạn không biết kích thước chính xác của bộ nhớ bạn cần sử dụng, bạn cần cấp phát động ( malloc). Ví dụ có thể là khi người dùng mở một tệp trong ứng dụng của bạn. Bạn sẽ cần đọc nội dung của tệp vào bộ nhớ, nhưng tất nhiên bạn không biết trước kích thước của tệp, vì người dùng chọn tệp tại chỗ, trong thời gian chạy. Vì vậy, về cơ bản bạn cần mallockhi không biết trước kích thước dữ liệu mà bạn đang làm việc. Ít nhất đó là một trong những lý do chính để sử dụng malloc. Trong ví dụ của bạn với một chuỗi đơn giản mà bạn đã biết kích thước tại thời điểm biên dịch (cộng với việc bạn không muốn sửa đổi nó), sẽ không có ý nghĩa gì khi phân bổ động chuỗi đó.


Hơi lạc đề, nhưng ... bạn phải hết sức cẩn thận để không tạo rò rỉ bộ nhớ khi sử dụng malloc. Hãy xem xét mã này:

int do_something() {
    uint8_t* someMemory = (uint8_t*)malloc(1024);

    // Do some stuff

    if ( /* some error occured */ ) return -1;

    // Do some other stuff

    free(someMemory);
    return result;
}

Bạn có thấy điều gì sai với mã này không? Có một câu lệnh trả về có điều kiện giữa mallocfree. Thoạt nghe có vẻ ổn, nhưng hãy nghĩ về nó. Nếu có lỗi, bạn sẽ quay lại mà không cần giải phóng bộ nhớ đã cấp. Đây là một nguồn rò rỉ bộ nhớ phổ biến.

Tất nhiên đây là một ví dụ rất đơn giản và bạn rất dễ thấy lỗi ở đây, nhưng hãy tưởng tượng hàng trăm dòng mã rải rác với con trỏ, mallocs, frees và tất cả các loại xử lý lỗi. Mọi thứ có thể trở nên lộn xộn rất nhanh. Đây là một trong những lý do tôi thích C ++ hiện đại hơn C trong các trường hợp áp dụng, nhưng đó là một chủ đề hoàn toàn khác.

Vì vậy, bất cứ khi nào bạn sử dụng malloc, hãy luôn đảm bảo rằng bộ nhớ của bạn có khả năng hoạt động freetốt nhất có thể.


Ví dụ xuất sắc!
Tốt lắm

6
char *some_memory = "Hello World";
sprintf(some_memory, "Goodbye...");

là bất hợp pháp, chuỗi ký tự là const.

Điều này sẽ phân bổ một mảng char 12 byte trên ngăn xếp hoặc trên toàn cục (tùy thuộc vào nơi nó được khai báo).

char some_memory[] = "Hello World";

Nếu bạn muốn chừa chỗ để thao tác thêm, bạn có thể chỉ định rằng mảng phải được kích thước lớn hơn. (Tuy nhiên, vui lòng không đặt 1MB trên ngăn xếp.)

#define LINE_LEN 80

char some_memory[LINE_LEN] = "Hello World";
strcpy(some_memory, "Goodbye, sad world...");
printf("%s\n", some_memory);

5

Một lý do khi cần cấp phát bộ nhớ là nếu bạn muốn sửa đổi nó trong thời gian chạy. Trong trường hợp đó, một malloc hoặc một bộ đệm trên ngăn xếp có thể được sử dụng. Ví dụ đơn giản về việc gán "Hello World" cho một con trỏ xác định bộ nhớ mà "thường" không thể sửa đổi trong thời gian chạy.

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.