Tiếp nối những kiến thức cơ bản về con trỏ, bài viết hôm nay sẽ đưa bạn đến với những kỹ thuật “thực chiến” hơn: làm thế nào để xin cấp phát bộ nhớ linh hoạt trong lúc chương trình đang chạy, cách quản lý con trỏ bậc cao và đặc biệt là bộ quy tắc Coding Convention để mã nguồn của bạn đạt chuẩn công nghiệp.
🎯 Mục tiêu học tập
- Thành thạo kỹ thuật Cấp phát bộ nhớ động (Dynamic Memory Allocation).
- Hiểu và sử dụng được Con trỏ cấp 2 (Pointer to Pointer).
- Phân biệt chính xác giữa Con trỏ hằng và Hằng con trỏ.
- Áp dụng bộ quy tắc trình bày chương trình (Coding Convention) theo chuẩn doanh nghiệp.
1. Cấp phát bộ nhớ động (Dynamic Memory Allocation)
Trong thực tế, không phải lúc nào chúng ta cũng biết trước kích thước của mảng dữ liệu. Cấp phát bộ nhớ động cho phép chương trình yêu cầu hệ thống cấp phát một vùng nhớ có kích thước tùy ý trên vùng nhớ Heap trong quá trình thực thi.
Các hàm hỗ trợ trong thư viện stdlib.h:
- malloc(size_t size): Cấp phát một vùng nhớ có kích thước tính bằng byte. Giá trị trong vùng nhớ này là giá trị rác.
- calloc(n, size): Cấp phát vùng nhớ cho
n phần tử, mỗi phần tử có kích thước size. Các ô nhớ được khởi tạo giá trị 0.
- realloc(ptr, size): Thay đổi kích thước của vùng nhớ đã cấp phát trước đó.
- free(ptr): Giải phóng vùng nhớ về cho hệ thống. Cực kỳ quan trọng: Quên dùng
free() sẽ dẫn đến lỗi Memory Leak (rò rỉ bộ nhớ).
// Ví dụ cấp phát mảng động
int arr; int n = 10; arr = (int)malloc(n * sizeof(int)); // Xin cấp phát vùng nhớ cho 10 số nguyên
if (arr == NULL) {
printf("Khong the cap phat bo nho!");
} else {
// Thao tác với mảng...
free(arr); // Luôn giải phóng bộ nhớ sau khi dùng xong
}
2. Con trỏ cấp 2 (Pointer to Pointer)
Con trỏ cấp 2 là một biến con trỏ dùng để lưu trữ địa chỉ của một con trỏ khác. Nó thường được dùng trong các bài toán mảng hai chiều động hoặc khi muốn thay đổi giá trị của một con trỏ bên trong một hàm.
int a = 10;
int *p1 = &a; // p1 trỏ tới a
int **p2 = &p1; // p2 trỏ tới p1
printf("Gia tri cua a qua p2: %d", **p2); // Output: 10
3. Con trỏ hằng và Hằng con trỏ
Đây là phần dễ gây nhầm lẫn nhất khi sử dụng từ khóa const với con trỏ. Hãy phân biệt kỹ hai trường hợp sau:
| Loại |
Khai báo |
Đặc tính |
| Con trỏ tới hằng (Pointer to Const) |
const int *p; |
Có thể đổi địa chỉ trỏ tới, nhưng KHÔNG thể đổi giá trị tại ô nhớ đó qua con trỏ. |
| Hằng con trỏ (Const Pointer) |
int * const p = &a; |
Có thể đổi giá trị ô nhớ, nhưng KHÔNG thể đổi địa chỉ mà con trỏ đang trỏ tới. |
4. Quy ước trình bày chương trình (Coding Convention)
Tại AIoT Systems, việc tuân thủ quy chuẩn viết code giúp các kỹ sư làm việc nhóm hiệu quả hơn và giảm thiểu sai sót. Dưới đây là các quy tắc cốt lõi:
a) Đặt tên (Naming Convention)
- Biến: Sử dụng
camelCase (ví dụ: sensorValue) hoặc snake_case (ví dụ: temp_value). Tên biến phải có nghĩa.
- Hằng số: Sử dụng chữ IN HOA toàn bộ (ví dụ:
MAX_BUFFER_SIZE).
- Hàm: Tên hàm nên bắt đầu bằng động từ (ví dụ:
read_sensor_data()).
b) Cấu trúc file và Thụt lề (Indentation)
- Sử dụng 4 khoảng trắng (hoặc 1 Tab) cho mỗi cấp độ thụt lề trong các khối lệnh
if, for, while.
- Dấu ngoặc nhọn
{ } phải được đặt thống nhất (ví dụ: luôn đặt ở cuối dòng bắt đầu khối lệnh).
c) Chú thích (Comments)
- Luôn có khối chú thích ở đầu file mô tả: Tác giả, Mục đích của file, Ngày cập nhật.
- Mỗi hàm nên có mô tả về các tham số đầu vào (Input) và giá trị trả về (Output).
📝 Tóm tắt: Quản lý bộ nhớ động là kỹ thuật giúp chương trình linh hoạt nhưng yêu cầu sự cẩn thận tuyệt đối với hàm free(). Việc áp dụng Coding Convention không chỉ giúp code đẹp mà còn thể hiện phong cách làm việc chuyên nghiệp của một kỹ sư nhúng thực thụ.
🚀 Bài tập về nhà:
- Viết chương trình nhập vào mảng n phần tử (n nhập từ bàn phím) bằng cách cấp phát động.
- Sắp xếp mảng trên theo thứ tự tăng dần bằng cách sử dụng con trỏ.
- Giải phóng bộ nhớ và kiểm tra Memory Leak.
Gợi ý bài tiếp theo: Kiểu dữ liệu Struct
Người thực hiện: Nguyễn Đình Tuấn
Email: tuannguyen.aiot@gmail.com | Website: aiots.vn