Pointer Trong Ngôn Ngữ C Là Gì - 3 Facts & Myths Về Pointer
Pointer trong những năm vừa qua có vẻ là một meme khá là nổi tiếng trong cộng đồng lập trình. Trong bài viết này, HeyDevs sẽ giới thiệu cho bạn pointer là gì, và làm sáng tỏ 3 facts and myths về pointer mà bạn có thể chưa biết. Hãy cùng HeyDevs khám phá nhé!
Pointer là gì?
Pointer là một biến đặc biệt trong ngôn ngữ C, có khả năng lưu trữ địa chỉ của một biến khác. Bạn có thể hiểu pointer như một chiếc bút chỉ, chỉ vào vị trí của một biến trong bộ nhớ máy tính. Ví dụ, nếu bạn có một biến số nguyên int x = 10;
, và một pointer int *p;
, bạn có thể gán địa chỉ của x
cho p
bằng cách dùng toán tử &
(lấy địa chỉ) như sau: p = &x;
. Lúc này, pointer p
sẽ trỏ đến biến x
, và bạn có thể truy cập giá trị của x
thông qua p
bằng cách dùng toán tử *
(lấy giá trị) như sau: printf("%d\n", *p);
. Kết quả sẽ là 10.
Pointer là một khái niệm quan trọng và mạnh mẽ trong ngôn ngữ C, vì nó cho phép bạn thao tác trực tiếp với bộ nhớ máy tính, và tạo ra những chương trình hiệu quả và linh hoạt. Tuy nhiên, pointer cũng là một nguồn gây ra nhiều lỗi và khó hiểu cho nhiều lập trình viên. Để hiểu rõ hơn về pointer, hãy cùng HeyDevs xem qua 3 facts and myths về pointer sau đây.
3 facts and myths về pointer
Fact 1: Pointer có thể trỏ đến bất kỳ kiểu dữ liệu nào
Đây là một sự thật về pointer. Bạn có thể khai báo pointer cho bất kỳ kiểu dữ liệu nào, từ kiểu cơ bản như int
, char
, float
, đến kiểu phức tạp như struct
, union
, hay thậm chí là pointer khác. Ví dụ, bạn có thể khai báo một pointer trỏ đến một ký tự như sau: char *c;
, hoặc một pointer trỏ đến một con trỏ khác như sau: int **pp;
. Tuy nhiên, bạn cần chú ý rằng khi khai báo pointer, bạn phải chỉ rõ kiểu dữ liệu của biến mà pointer trỏ đến, để máy tính biết cách xử lý giá trị của pointer. Ví dụ, nếu bạn có một biến số nguyên int x = 10;
, và một pointer char *c = &x;
, khi bạn in ra giá trị của *c
, bạn sẽ không nhận được kết quả mong muốn, vì máy tính sẽ hiểu rằng c
là một pointer trỏ đến một ký tự, chứ không phải là một số nguyên.
Myth 1: Pointer luôn có giá trị là NULL
Đây là một điều sai lầm về pointer. Pointer không phải luôn luôn có giá trị là NULL (địa chỉ không hợp lệ) khi được khai báo. Thực tế, giá trị của pointer khi được khai báo phụ thuộc vào cách bạn khai báo nó. Nếu bạn khai báo pointer mà không gán giá trị cho nó, ví dụ int *p;
, thì pointer đó sẽ có giá trị là rác (garbage value), tức là một địa chỉ ngẫu nhiên trong bộ nhớ, có thể là hợp lệ hoặc không. Nếu bạn khai báo pointer và gán giá trị cho nó, ví dụ int *p = &x;
, thì pointer đó sẽ có giá trị là địa chỉ của biến x
. Nếu bạn muốn khai báo pointer và gán giá trị là NULL cho nó, bạn phải làm rõ điều đó, ví dụ int *p = NULL;
. Việc gán giá trị NULL cho pointer là một thói quen tốt, vì nó giúp bạn kiểm tra được pointer có trỏ đến biến hợp lệ hay không, và tránh được những lỗi do sử dụng pointer rác.
Fact 2: Pointer có thể được cấp phát động
Đây là một sự thật về pointer. Bạn có thể sử dụng các hàm cấp phát động như malloc
, calloc
, realloc
để cấp phát bộ nhớ cho pointer, và sử dụng hàm free
để giải phóng bộ nhớ khi không cần thiết. Việc cấp phát động cho pointer cho phép bạn tạo ra những cấu trúc dữ liệu động như danh sách liên kết, cây nhị phân, ngăn xếp, hàng đợi, v.v. Ví dụ, bạn có thể tạo một mảng động bằng cách cấp phát bộ nhớ cho một pointer như sau: int *arr = (int *)malloc(n * sizeof(int));
, trong đó n
là số phần tử của mảng. Lúc này, bạn có thể sử dụng pointer arr
như một mảng thông thường, và truy cập các phần tử của nó bằng cách dùng chỉ số, ví dụ arr[0]
, arr[1]
, v.v. Tuy nhiên, bạn cần chú ý rằng khi cấp phát động cho pointer, bạn phải kiểm tra xem việc cấp phát có thành công hay không, và khi không sử dụng pointer nữa, bạn phải giải phóng bộ nhớ để tránh rò rỉ bộ nhớ (memory leak).
Myth 2: Pointer và mảng là một
Đây là một điều sai lầm về pointer. Pointer và mảng có một số điểm tương đồng, nhưng chúng không phải là một. Một số điểm tương đồng giữa pointer và mảng là:
- Cả hai đều liên quan đến bộ nhớ của máy tính.
- Cả hai đều có thể được sử dụng để lưu trữ nhiều giá trị của cùng một kiểu dữ liệu.
- Cả hai đều có thể được truy cập bằng cách dùng chỉ số hoặc toán tử
*
.
Tuy nhiên, một số điểm khác biệt giữa pointer và mảng là:
- Pointer là một biến, có thể thay đổi giá trị của nó (địa chỉ mà nó trỏ đến), trong khi mảng là một hằng số (constant), không thể thay đổi giá trị của nó (địa chỉ của phần tử đầu tiên).
- Pointer có thể được cấp phát động hoặc tĩnh, trong khi mảng chỉ có thể được cấp phát tĩnh (trừ khi sử dụng con trỏ để tạo mảng động động). Điều này có nghĩa là pointer có thể thay đổi kích thước của nó, trong khi mảng không thể.
- Pointer có thể trỏ đến bất kỳ kiểu dữ liệu nào, trong khi mảng chỉ có thể lưu trữ các giá trị của cùng một kiểu dữ liệu.
Vì vậy, bạn không nên nhầm lẫn pointer và mảng là một, mà hãy hiểu rõ sự khác biệt và cách sử dụng của chúng.
Fact 3: Pointer có thể được truyền vào hàm hoặc trả về từ hàm
Đây là một sự thật về pointer. Bạn có thể sử dụng pointer để truyền vào hàm hoặc trả về từ hàm, nhằm mục đích thay đổi giá trị của biến trong hàm gọi, hoặc trả về một địa chỉ bộ nhớ từ hàm. Việc sử dụng pointer để truyền vào hàm hoặc trả về từ hàm là một kỹ thuật hiệu quả và tiết kiệm bộ nhớ, vì bạn không cần phải sao chép giá trị của biến, mà chỉ cần truyền địa chỉ của nó. Ví dụ, bạn có thể viết một hàm để hoán đổi giá trị của hai biến số nguyên như sau:
void swap(int *a, int *b) {
int temp = *a;
*a = *b;
*b = temp;
}
Hàm này nhận vào hai con trỏ a
và b
, và thay đổi giá trị của hai biến mà chúng trỏ đến. Bạn có thể gọi hàm này như sau:
int x = 10, y = 20;
swap(&x, &y);
printf("%d %d\n", x, y); // 20 10
Bạn cũng có thể viết một hàm để tạo ra một mảng động và trả về con trỏ của nó như sau:
int *create_array(int n) {
int *arr = (int *)malloc(n * sizeof(int));
if (arr == NULL) {
printf("Memory allocation failed\n");
exit(1);
}
for (int i = 0; i < n; i++) {
arr[i] = i + 1;
}
return arr;
}
Hàm này nhận vào số phần tử n
, và cấp phát bộ nhớ cho một con trỏ arr
, sau đó gán giá trị cho các phần tử của nó, và cuối cùng trả về con trỏ arr
. Bạn có thể gọi hàm này như sau:
int *p = create_array(5);
for (int i = 0; i < 5; i++) {
printf("%d ", p[i]); // 1 2 3 4 5
}
free(p);
Myth 3: Pointer luôn luôn là an toàn
Đây là một điều sai lầm về pointer. Pointer không phải luôn luôn là an toàn, mà có thể gây ra nhiều lỗi và nguy hiểm nếu không được sử dụng cẩn thận. Một số lỗi và nguy hiểm do sử dụng pointer là:
- Sử dụng pointer rác: Nếu bạn sử dụng pointer mà không gán giá trị cho nó, hoặc gán giá trị sai cho nó, bạn có thể gặp phải lỗi khi truy cập giá trị của pointer. Ví dụ, nếu bạn có một con trỏ
int *p;
, và bạn in ra giá trị của*p
, bạn có thể gặp phải lỗi segmentation fault (truy cập vào vùng nhớ không hợp lệ), hoặc in ra một giá trị rác không mong muốn. - Sử dụng pointer sau khi giải phóng: Nếu bạn sử dụng pointer sau khi bạn đã giải phóng bộ nhớ của nó, bạn cũng có thể gặp phải lỗi khi truy cập giá trị của pointer. Ví dụ, nếu bạn có một con trỏ
int *p = (int *)malloc(sizeof(int));
, và bạn gán giá trị cho nó*p = 10;
, sau đó bạn giải phóng bộ nhớ của nófree(p);
, và bạn in ra giá trị của*p
, bạn cũng có thể gặp phải lỗi segmentation fault, hoặc in ra một giá trị rác không mong muốn. Điều này là vì sau khi bạn giải phóng bộ nhớ của pointer, địa chỉ mà pointer trỏ đến có thể được sử dụng bởi các biến khác, hoặc không còn hợp lệ nữa. - Sử dụng pointer vượt quá giới hạn: Nếu bạn sử dụng pointer để truy cập vào các vị trí bộ nhớ ngoài phạm vi cho phép, bạn cũng có thể gây ra lỗi hoặc ảnh hưởng đến các biến khác. Ví dụ, nếu bạn có một mảng
int arr[5] = {1, 2, 3, 4, 5};
, và một con trỏint *p = arr;
, và bạn in ra giá trị của*(p + 5)
, bạn có thể gặp phải lỗi segmentation fault, hoặc in ra một giá trị rác không mong muốn. Điều này là vì*(p + 5)
là vị trí bộ nhớ ngoài phạm vi của mảngarr
, và có thể là vùng nhớ không hợp lệ, hoặc là vùng nhớ của biến khác.
Vì vậy, bạn không nên coi pointer là an toàn, mà hãy sử dụng pointer một cách cẩn thận và chính xác.
Kết luận
Pointer là một khái niệm quan trọng và mạnh mẽ trong ngôn ngữ C, nhưng cũng là một nguồn gây ra nhiều lỗi và khó hiểu cho nhiều lập trình viên. Trong bài viết này, HeyDevs đã giới thiệu cho bạn pointer là gì, và làm sáng tỏ 3 facts and myths về pointer mà bạn có thể chưa biết. Hy vọng bài viết này sẽ giúp bạn hiểu rõ hơn về pointer, và sử dụng pointer một cách hiệu quả và an toàn trong chương trình của bạn.
Nếu bạn thấy bài viết này hữu ích, hãy theo dõi HeyDevs để có thể đọc thêm những bài viết khác trong series gõ myth về ngôn ngữ lập trình nhé. Cảm ơn các bạn! :-)
Về HeyDevs
HeyDevs là nền tảng tìm việc IT thụ động đầu tiên ở khu vực APAC, giúp bạn xua tan những nỗi lo trong quá trình xin việc. HeyDevs cung cấp trải nghiệm tuyển dụng hoàn toàn mới, giờ đây bạn không cần phải nộp đơn xin việc, các công ty sẽ ứng tuyển vào bạn.
Với lập trình viên, HeyDevs cung cấp các tính năng đặc biệt như nút lệnh “sẵn sàng làm việc", hồ sơ ẩn danh, tạo hồ sơ với CV có sẵn với khả năng kết nối và trò chuyện với các nhà tuyển dụng được tích hợp sẵn trên nền tảng.
Với nhà tuyển dụng, HeyDevs cung cấp các công cụ hợp lý hóa quy trình tuyển dụng, giúp tiết kiệm thời gian và tiền bạc, với quy trình tuyển dụng được tinh giản đến mức tối ưu. HeyDevs cho phép công ty tiếp cận với nhóm ứng viên dồi dào, chất lượng và được xác minh danh tính, trình độ cũng như kinh nghiệm làm việc, giúp nâng cao hiệu quả tuyển dụng của doanh nghiệp.