[Embedded Series – Bài 5] Con Trỏ (Pointer) Và Quy Ước Trình Bày Chương Trình
Trong lập trình C, đặc biệt là lập trình Nhúng, Con trỏ (Pointer) là một trong những phần kiến thức quan trọng và mạnh mẽ nhất. Bài viết này sẽ giúp bạn hiểu rõ bản chất của con trỏ, cách nó tương tác với mảng, hàm và những quy tắc để viết mã nguồn chuyên nghiệp tại AIoT Systems.
🎯 Mục tiêu học tập
- Nắm vững khái niệm con trỏ, địa chỉ và giá trị của vùng nhớ.
- Thành thạo khai báo, gán giá trị và sử dụng con trỏ để thay đổi dữ liệu gián tiếp.
- Hiểu mối quan hệ mật thiết giữa con trỏ và mảng.
- Phân biệt truyền tham trị và truyền tham chiếu trong hàm.
- Nhận biết rủi ro khi hàm trả về địa chỉ của biến cục bộ.
1. Con trỏ (Pointer)
a) Khái niệm
Con trỏ trong C cũng chỉ là một biến. Nó có thể được khai báo, khởi tạo và lưu trữ giá trị, đồng thời có địa chỉ của riêng nó. Tuy nhiên, thay vì lưu trữ các giá trị thông thường (như số nguyên, ký tự), biến con trỏ lưu trữ địa chỉ của một biến khác trong RAM.
Các khái niệm cần thống nhất:
- Kiểu dữ liệu của con trỏ: Trùng với kiểu dữ liệu tại vùng nhớ mà nó trỏ đến.
- Giá trị của con trỏ: Chứa địa chỉ vùng nhớ mục tiêu.
- Địa chỉ của con trỏ: Địa chỉ của bản thân biến con trỏ đó trong RAM.
b) Cách khai báo con trỏ
Khai báo giống như biến bình thường nhưng có thêm dấu :
<kiểu dữ liệu> * <tên biến>;
- Kiểu dữ liệu:
void, char, int, float, double…
- Dấu
trước tên biến báo cho trình biên dịch biết đây là một con trỏ.
c) Gán giá trị cho con trỏ
Sau khi khai báo, bạn phải khởi tạo giá trị cho con trỏ. Nếu sử dụng con trỏ chưa khởi tạo, nó sẽ chứa giá trị rác, có thể khiến chương trình chạy sai hoặc treo hệ thống (cực kỳ nguy hiểm trong lập trình nhúng).
int *p, value;
value = 5;
p = &value; // Gán địa chỉ của value cho con trỏ p
// Hoặc khai báo và khởi tạo đồng thời
int *p_int = NULL; // Khởi tạo bằng NULL nếu chưa dùng ngay
d) Tác dụng của con trỏ
Con trỏ cho phép thay đổi giá trị vùng nhớ mà nó trỏ đến một cách gián tiếp.
Ví dụ, để đổi giá trị biến c = 22 thành 11:
- Cách 1: Thay đổi trực tiếp:
c = 11;
- Cách 2: Thay đổi qua con trỏ:
int *pc = &c; *pc = 11; (Lúc này *pc chính là giá trị của c).
2. Con trỏ và Mảng
Mảng và con trỏ có mối quan hệ rất chặt chẽ. Khi khai báo int x[4], tên mảng x chính là địa chỉ của phần tử đầu tiên.
int x[4];
int *p = x; // Tương đương p = &x[0]
Dựa vào sơ đồ bộ nhớ, ta có các tương đương sau:
&x[i] tương đương với p + i.
x[i] tương đương với *(p + i) hoặc p[i].
Ví dụ minh họa:
int a[4] = {1, 2, 3, 4};
int *p = a;
*p = 11; // a[0] = 11
*(p + 1) = 12; // a[1] = 12
*(p + 2) = 13; // a[2] = 13
*(p + 3) = 14; // a[3] = 14
3. Con trỏ và Hàm
a) Truyền địa chỉ biến vào con trỏ (Tham chiếu)
Khi truyền tham trị (truyền giá trị), hàm sẽ tạo bản sao nên không thay đổi được biến gốc. Để thay đổi giá trị biến sau khi hàm thực hiện xong, ta phải truyền vào địa chỉ (tham chiếu).
| Hoán vị Tham Trị (Sai) |
Hoán vị Tham Chiếu (Đúng) |
void hoanVi_thamtri(int a, int b) {
int temp = a;
a = b;
b = temp;
}
|
void hoanVi_thamchieu(int *a, int *b) {
int temp = *a;
*a = *b;
*b = temp;
}
|
Hàm hoanVi_thamchieu tác động trực tiếp vào vùng địa chỉ của biến gốc trên RAM nên giá trị thực tế sẽ được hoán đổi.
b) Hàm trả về một con trỏ
Cấu pháp: kieu_tra_ve * ten_ham() { ... }
Cảnh báo (Ví dụ 1): Trong một hàm, nếu bạn khai báo biến cục bộ (ví dụ int x, y) và trả về địa chỉ của chúng, địa chỉ này sẽ không còn ý nghĩa khi hàm kết thúc (biến cục bộ bị giải phóng khỏi Stack). Việc truy cập vào địa chỉ này sẽ gây ra lỗi nghiêm trọng.
⚠️ Lưu ý cực kỳ quan trọng: Tuyệt đối không trả về địa chỉ của một biến cục bộ từ trong hàm.
📝 Tóm tắt: Con trỏ là biến lưu trữ địa chỉ vùng nhớ. Nó giúp thao tác trực tiếp với RAM, quản lý mảng linh hoạt và cho phép hàm thay đổi giá trị biến bên ngoài. Tuy nhiên, cần chú ý khởi tạo con trỏ và tránh lỗi trả về địa chỉ biến cục bộ.
Gợi ý bài tiếp theo: Cấp phát bộ nhớ động và Quy ước trình bày chương trình (Coding Convention).
Người thực hiện: Nguyễn Đình Tuấn
Email: tuannguyen.aiot@gmail.com | Website: aiots.vn