Bài 66. Cấp phát bộ nhớ động trong C

Cấp Phát Bộ Nhớ Động
Cấp Phát Bộ Nhớ Động

Nói đến con trỏ không thể không nhắc tới cấp phát bộ nhớ độnggiải phóng bộ nhớ cho biến con trỏ trong ngôn ngữ C. Trong bài viết này, Lập trình không khó sẽ làm rõ vấn đề cấp phát bộ nhớ động sử dụng malloc(), calloc(), free()realloc() trong C. Đây là các hàm giúp các bạn cấp phát và giải phóng bộ nhớ khi làm việc với con trỏ trong ngôn ngữ C.

Tại sao cần cấp phát bộ nhớ động?

Như bạn đã biết, mảng là một tập hợp của các phần tử nằm liên tiếp nhau trên bộ nhớ và có cùng kiểu dữ liệu. Khi khai báo mảng, bạn phải chỉ định rõ kích thước tối đa (số lượng phần tử tối đa). Và sau khi khai báo, bạn không thể thay đổi kích thước của mảng – Cấp phát tĩnh.

Đôi khi kích thước của mảng bạn khai báo có thể không đủ sài. Để giải quyết vấn đề này, bạn có thể cấp phát thêm bộ nhớ theo cách thủ công trong thời gian chạy chương trình. Đó cũng chính là khái niệm cấp phát động trong C.

Bảng dưới đây so sánh giúp bạn sự khác biệt giữa cấp phát bộ nhớ động và tĩnh.

Cấp phát bộ nhớ tĩnhCấp phát bộ nhớ động
Bộ nhớ được cấp phát trước khi chạy chương trình (trong quá trình biên dịch)Bộ nhớ được cấp phát trong quá trình chạy chương trình.
Không thể cấp phát hay phân bổ lại bộ nhớ trong khi chạy chương trìnhCho phép quản lý, phân bổ hay giải phóng bộ nhớ trong khi chạy chương trình
Vùng nhớ được cấp phát và tồn tại cho đến khi kết thúc chương trìnhChỉ cấp phát vùng nhớ khi cần sử dụng tới
Chương trình chạy nhanh hơn so với cấp phát độngChương trình chạy chậm hơn so với cấp phát tĩnh
Tốn nhiều không gian bộ nhớ hơnTiết kiệm được không gian bộ nhớ sử dụng

Ưu điểm chính của việc sử dụng cấp phát động là giúp ta tiết kiệm được không gian bộ nhớ mà chương trình sử dụng. Bởi vì chúng ta sẽ chỉ cấp phát khi cần dùng và có thể giải phóng vùng nhớ đó ngay sau khi sử dụng xong.

Nhược điểm chính của cấp phát động là bạn phải tự quản lý vùng nhớ mà bạn cấp phát. Nếu bạn cứ cấp phát mà quên giải phóng bộ nhớ thì chương trình của bạn sẽ tiêu thụ hết tài nguyên của máy tính dẫn đến tình trạng tràn bộ nhớ (memory leak).

Cấp phát bộ nhớ động trong C

Để cấp phát vùng nhớ động cho biến con trỏ trong ngôn ngữ C, bạn có thể sử dụng hàm malloc() hoặc hàm calloc(). Sử dụng hàm free() để giải phóng bộ nhớ đã cấp phát khi không cần sử dụng, sử dụng realloc() để thay đổi (phân bổ lại) kích thước bộ nhớ đã cấp phát trong khi chạy chương trình.

Sử dụng hàm malloc()

Từ malloc là đại diện cho cụm từ memory allocation (dịch: cấp phát bộ nhớ).

Hàm malloc() thực hiện cấp phát bộ nhớ bằng cách chỉ định số byte cần cấp phát. Hàm này trả về con trỏ kiểu void cho phép chúng ta có thể ép kiểu về bất cứ kiểu dữ liệu nào.

Cú pháp của hàm malloc():

Ví dụ:

Ví dụ trên thực hiện cấp phát cho việc lưu trữ 100 số nguyên. Giả sử sizeof int là 4, khi đó lệnh dưới đây thực hiện cấp phát 400 bytes. Khi đó, con trỏ ptr sẽ có giá trị là địa chỉ của byte dữ liệu đầu tiên trong khối bộ nhớ vừa cấp phát.

Trong trường hợp không thể cấp phát bộ nhớ, nó sẽ trả về một con trỏ NULL.

Sử dụng hàm calloc()

Từ calloc đại diện cho cụm từ contiguous allocation (dịch: cấp phát liên tục).

Hàm malloc() khi cấp phát bộ nhớ thì vùng nhớ cấp phát đó không được khởi tạo giá trị ban đầu. Trong khi đó, hàm calloc() thực hiện cấp phát bộ nhớ và khởi tạo tất cả các ô nhớ có giá trị bằng 0.

Hàm calloc() nhận vào 2 tham số là số ô nhớ muốn khởi tạo và kích thước của 1 ô nhớ.

Cú pháp của hàm calloc():

Ví dụ:

Trong ví dụ trên, hàm calloc() thực hiện cấp phát 100 ô nhớ liên tiếp và mỗi ô nhớ có kích thước là số byte của kiểu int. Hàm này cũng trả về con trỏ chứa giá trị là địa chỉ của byte đầu tiên trong khối bộ nhớ vừa cấp phát.

Sử dụng hàm free()

Việc cấp phát bộ nhớ động trong C dù sử dụng malloc() hay calloc() thì chúng cũng đều không thể tự giải phóng bộ nhớ. Bạn cần sử dụng hàm free() để giải phóng vùng nhớ.

Cú pháp:

Lệnh này sẽ giải phóng vùng nhớ mà con trỏ ptr đã được cấp phát. Giải phóng ở đây có nghĩa là trả lại vùng nhớ đó cho hệ điều hành và hệ điều hành có thể sử dụng vùng nhớ đó vào việc khác nếu cần.

Nếu bạn không giải phóng nó thì nó sẽ tồn tại cho tới khi chương trình kết thúc. Điều này sẽ rất nguy hiểm nếu chương trình của bạn liên tục cấp phát các vùng nhớ mới và sẽ gây ra hiện tượng tràn bộ nhớ mà mình đã nhắc tới ở trên. Thử code này xem sao (bảo đảm là máy bạn sẽ bị treo và chỉ còn cách ấn nút nguồn thôi, bạn có thể để cấp phát size nhỏ hơn và theo dõi thay đổi qua Task Manager):

Ví dụ sử dụng malloc() và free()

Trong ví dụ dưới đây, chúng ta sẽ sử dụng hàm malloc() để cấp phát động n * sizeof int byte và sử dụng xong sẽ dùng free() để giải phóng.

Ví dụ sử dụng calloc() và free()

Trong ví dụ này, chúng ta sẽ dùng calloc() để cấp phát n ô nhớ liên tiếp và mỗi ô nhớ có kích thước là sizeof int. Lưu ý là hàm calloc() sẽ chậm hơn malloc() một chút do nó phải thêm bước khởi tạo các ô nhớ có giá trị bằng 0. Do đó, tùy thuộc bạn cần hiệu năng hay cần khởi tạo giá trị ban đầu mà sử dụng hàm cấp phát thích hợp.

Sử dụng hàm realloc()

Nếu việc cấp phát bộ nhớ động không đủ hoặc cần nhiều hơn mức đã cấp phát, bạn có thể thay đổi kích thước của bộ nhớ đã được cấp phát trước đó bằng cách sử dụng hàm realloc().

Cú pháp của realloc():

Hàm này thực hiện cấp phát vùng nhớ mới cho con trỏ ptr. Vùng nhớ mới đó sẽ có kích thước mới là n bytes.

Hàm này cũng trả về con trỏ chứa giá trị là địa chỉ của byte đầu tiên trong vùng nhớ mới. Hàm này sẽ cố gắng mở rộng số ô nhớ ra phía sau nếu có thể để giữ nguyên giá trị của con trỏ ban đầu. Trong trường hợp phải đổi sang một vùng nhớ khác, hàm realloc() cũng sẽ mang theo giá trị đã có ở vùng nhớ cũ sang vùng nhớ mới và giải phóng luôn vùng nhớ cũ (đọc thêm tài liệu số 2). Trong trường hợp không thể, nó sẽ trả về con trỏ NULL giống như malloc()calloc().

Ví dụ sử dụng hàm realloc()

Trong ví dụ dưới đây, ta sẽ sử dụng hàm realloc() để tái phân bổ lại bộ nhớ. Như trong ví dụ dưới đây thì việc cấp phát không phải di chuyển sang vùng nhớ khác mà chỉ mở rộng ra phía sau.

Kết quả chạy:

Cấp phát động trong C++

Mục này không phải kiến thức của C, nhưng mình cũng sẽ ghi lại để các bạn học luôn hoặc tìm hiểu, phân biệt và sử dụng trong C++ khi cần.

Để cấp phát động trong C++ bạn sử dụng toán tử new và giải phóng bộ nhớ sử dụng delete. Ví dụ:

Sau đây mình sẽ hướng dẫn bạn cách sử dụng cấp phát động vào mảng 1 chiều.

Cấp phát động mảng 1 chiều

Dưới đây là ví dụ sử dụng cấp phát động cho mảng 1 chiều để các bạn tham khảo và nắm được ứng dụng của cấp phát bộ nhớ động.

Kết quả chạy chương trình:

Tài liệu tham khảo

  1. C Dynamic Memory Allocation
  2. Changing Block Size (The GNU C Library)
avatar
  Subscribe  
newest oldest most voted
Notify of
Phu Nguyen
Guest
Phu Nguyen

Anh Hiếu cho em hỏi, em dùng VS Code để chạy ví dụ phần Realloc tìm hiểu địa chỉ của ptr, thì lỗi “format %u type ‘unsigned int’ khác ptr type ‘int *'”. Nên em chạy không được các dạng in địa chỉ tương tự. Mong anh giải đáp!

Khoa
Guest
Khoa

Anh ơi cho e hỏi “exit;” với “exit(0);” khác nhau như thế nào vậy ạ