Bộ nhớ động được cấp phát trên vùng heap, khác với bộ nhớ tĩnh trên stack. Bài viết này sẽ giúp bạn hiểu rõ các hàm quản lý bộ nhớ động và những lỗi thường gặp.
Các Hàm Quản lý Bộ nhớ Động
1. malloc và free
malloc cấp phát một khối bộ nhớ với kích thước chỉ định (tính bằng byte) nhưng không khởi tạo giá trị. Hàm trả về con trỏ void*, cần ép kiểu phù hợp. free giải phóng bộ nhớ đã cấp phát.
#include <stdio.h>
#include <stdlib.h>
int main() {
// Cấp phát 40 byte (đủ cho 10 số nguyên)
int* data = (int*)malloc(40);
if (data == NULL) {
perror("malloc");
return 1;
}
// In giá trị (chưa được khởi tạo)
for (int i = 0; i < 10; i++) {
printf("%d\n", data[i]);
}
free(data); // Giải phóng bộ nhớ
return 0;
}
2. calloc
calloc cấp phát bộ nhớ cho một mảng gồm num phần tử, mỗi phần tử có kích thước size byte, và tự động khởi tạo tất cả về 0.
#include <stdio.h>
#include <stdlib.h>
int main() {
// Cấp phát mảng 10 số nguyên, tất cả bằng 0
int* numbers = (int*)calloc(10, sizeof(int));
if (numbers == NULL) {
perror("calloc");
return 1;
}
for (int i = 0; i < 10; i++) {
printf("%d\n", numbers[i]); // In ra toàn 0
}
free(numbers);
numbers = NULL;
return 0;
}
3. realloc
realloc thay đổi kích thước của một khối bộ nhớ đã được cấp phát trước đó. Nó có thể mở rộng hoặc thu hẹp vùng nhớ. Nội dung cũ được giữ nguyên, phần mở rộng không được khởi tạo.
#include <stdio.h>
#include <stdlib.h>
int main() {
int* arr = (int*)malloc(40);
if (arr == NULL) {
perror("malloc");
return 1;
}
// Khởi tạo 10 phần tử đầu
for (int i = 0; i < 10; i++) {
arr[i] = i + 1;
}
// Mở rộng lên 80 byte (20 số nguyên)
int* new_arr = (int*)realloc(arr, 80);
if (new_arr != NULL) {
arr = new_arr;
new_arr = NULL;
} else {
perror("realloc");
return 1;
}
// In 20 phần tử (10 phần tử mới có giá trị rác)
for (int i = 0; i < 20; i++) {
printf("%d\n", arr[i]);
}
free(arr);
arr = NULL;
return 0;
}
Các Lỗi Thường Gặp Khi Quản lý Bộ nhớ Động
1. Giải tham chiếu con trỏ NULL
Khi malloc thất bại (trả về NULL), việc gán giá trị sẽ gây lỗi.
void test() {
int* p = (int*)malloc(INT_MAX / 4);
*p = 20; // Lỗi nếu p là NULL
}
2. Truy cập vượt quá vùng nhớ đã cấp phát
Chỉ cấp phát 40 byte (10 số nguyên) nhưng truy cập 20 phần tử.
int main() {
int* p = (int*)malloc(40);
if (p == NULL) {
perror("malloc");
return 1;
}
for (int i = 0; i < 20; i++) { // Lỗi: vượt quá giới hạn
printf("%d\n", p[i]);
}
free(p);
return 0;
}
3. Giải phóng bộ nhớ không được cấp phát động
Không được dùng free cho biến tĩnh.
int main() {
int a = 0;
int* p = &a;
free(p); // Lỗi: a không phải bộ nhớ động
return 0;
}
4. Giải phóng một phần của vùng nhớ
Khi con trỏ bị thay đổi vị trí, free sẽ không giải phóng đúng vùng nhớ ban đầu.
int main() {
int* p = (int*)malloc(40);
if (p == NULL) {
perror("malloc");
return 1;
}
for (int i = 0; i < 5; i++) {
*p = i;
p++; // p không còn trỏ đến đầu vùng nhớ
}
free(p); // Lỗi: giải phóng phần giữa
return 0;
}
Cách khắc phục: Lưu lại con trỏ gốc.
int* original = p;
// ... xử lý ...
free(original);
5. Giải phóng nhiều lần cùng một vùng nhớ
Gọi free hai lần trên cùng một con trỏ (nếu không gán NULL sau lần đầu) gây lỗi.
int main() {
int* p = (int*)malloc(40);
// ... sử dụng ...
free(p);
free(p); // Lỗi: double free
return 0;
}
Cách khắc phục: Gán con trỏ về NULL sau khi giải phóng.
free(p);
p = NULL;
6. Rò rỉ bộ nhớ (Memory leak)
Không giải phóng bộ nhớ sau khi sử dụng, dẫn đến hao tốn tài nguyên.
void test() {
int* p = (int*)malloc(100);
if (p != NULL) {
*p = 20;
}
// Quên free(p) -> rò rỉ bộ nhớ
}
int main() {
test();
while (1); // Chương trình chạy vô hạn, bộ nhớ không được giải phóng
return 0;
}
Mảng Linh Hoạt (Flexible Array Member)
Trong C99, struct có thể chứa một mảng không xác định kích thước ở cuối, gọi là mảng linh hoạt. Kích thước của struct không bao gồm mảng này.
struct S {
int a;
int arr[]; // Mảng linh hoạt
};
Khi cấp phát, bạn cần cấp thêm bộ nhớ cho mảng:
struct S* ps = (struct S*)malloc(sizeof(struct S) + 10 * sizeof(int));
ps->a = 5;
for (int i = 0; i < 10; i++) {
ps->arr[i] = i;
}
free(ps);