Con trỏ chưa bao giờ là dễ dàng với người học, vì sự trừu tượng của nó, và vì có nhiều khái niệm liên quan đến bộ nhớ, địa chỉ… vốn xa lạ với người mới. Lời khuyên trước tiên là bạn hãy đọc qua bài này: https://daohainam.com/2021/08/13/cau-chuyen-ve-con-tro-pointer/
Sau khi đọc xong, ta sẽ điểm thêm vài điều có thể bạn chưa biết:
Trong ngôn ngữ máy không có khái niệm con trỏ:
Đúng vậy, con trỏ là một khái niệm của ngôn ngữ bậc cao. Trong ngôn ngữ máy hoặc assembly, bạn chỉ có khái niệm địa chỉ, vì đối với nó, địa chỉ cũng chỉ là một số nguyên nên chẳng có lý do gì lại phải có thêm một kiểu dữ liệu mới.Trong các ngôn ngữ bậc cao, người ta cần con trỏ để giúp xác định “kiểu của vùng nhớ mà con trỏ trỏ đến”.
Con trỏ có kích thước bằng với chế độ bộ nhớ khi dịch chương trình, không phụ thuộc vào nền tảng mà nó chạy trên đó:
Nếu bạn dịch ra chế độ 32 bit, chương trình của bạn sẽ là một chương trình 32 bit, nó chỉ có thể chạy trên các CPU và hệ điều hành 32 bit. Ngược lại nếu dịch ra dạng 64 bit thì nó chỉ chạy trên HĐH và CPU 64 bit. (https://www.facebook.com/namdotnet/posts/1297868437064836)Tuy nhiên, hầu hết các hệ điều hành và CPU 64 bit hiện đại đều cho phép tương thích ngược, giúp bạn có thể chạy được các ứng dụng 32 bit (vì thanh ghi 64 bit thì vẫn chứa được một địa chỉ 32 bit).
Con trỏ có thể trỏ đến kiểu void:
Kiểu void là kiểu “không quan tâm nó là kiểu gì”, khi bạn có một con trỏ chỉ để lưu trữ địa chỉ một vùng nhớ, mà không cần xử lý gì trên vùng nhớ đó, thì bạn dùng kiểu void*. Bạn xem một ví dụ về kiểu con trỏ void ở đây: https://godbolt.org/z/2T-7DkCác phần phía sau liên quan đến tổ chức bộ nhớ máy tính (môn kiến trúc máy tính):
Tất cả địa chỉ đều là hợp lệ, vấn đề là bạn có quyền truy cập nó hay không:
Trên lý thuyết, tất cả địa chỉ từ 0 đến (2 mũ 64 -1) đều là hợp lệ, tuy nhiên trong thực tế nó còn nhiều giới hạn khác khiến cho bạn không thể truy xuất vào một vùng nhớ nào đó, chẳng hạn:
– Một số địa chỉ là dành riêng, bạn không bao giờ được truy cập vì nó chỉ dành cho hệ điều hành và hệ thống. Một địa chỉ dành riêng mà ai cũng biết là địa chỉ 0, chính vì vậy trong các ứng dụng thì địa chỉ 0 (hay còn gọi là địa chỉ NULL) được dùng để chỉ ra đây là con trỏ KHÔNG hợp lệ.
– Giới hạn của đường địa chỉ….
Địa chỉ là tuyến tính, nhưng nơi lưu trữ thực tế là khác nhau:
Ta vẫn biết bộ nhớ là một dải từ địa chỉ 0 đến giới hạn tối đa mà hệ thống cho phép, tuy nhiên các phần trong dải địa chỉ này có thể ánh xạ vào những vùng nhớ hoàn toàn khác nhau, ví dụ: từ 0 đến 0xFFFF, là vào RAM, từ 0x10000 đến 0x2FFFF lại vào ROM, lên 0x30000 lại chẳng ánh xạ vào đâu cả. Lên 0x40000 có khi lại là RAM trên card đồ họa, rồi 0x90000 lại đang là page file… ví dụ vậy (và chỉ là ví dụ thôi nhé). Hệ điều hành sẽ quản lý việc địa chỉ nào thì ánh xạ vào đâu, khi bạn xin cấp phát, hoặc khi bạn truy cập vào 1 vùng nhớ HĐH sẽ kiểm tra và đảm bảo vùng nhớ đó đã được tải vào RAM nếu nó đang nằm trong page file.
Trong một số trường hợp, bạn vẫn có thể sử dụng lượng vùng nhớ lớn hơn dải địa chỉ cho phép:
Ví dụ trên các hệ thống 16 bit, vốn dùng cơ chế segment:offset và chỉ có thể truy cập được 1MB bộ nhớ, người ta vẫn có cách để sử dụng nhiều hơn. Các địa chỉ sẽ được ánh xạ vào một vùng trong bộ nhớ RAM, ta gọi vùng này là window. Ví dụ bạn sẽ dùng 1 window có kích thước 4KB, khi cần đọc/ghi, bạn sẽ di chuyển vùng window này đến nơi cần thiết, do vậy bạn vẫn chỉ dùng cùng một địa chỉ, nhưng nơi truy xuất thực tế sẽ là khác nhau (tìm hiểu thêm về EMS và AWE). Đây cũng là cách mà chúng ta dùng để vẽ vào bộ nhớ RAM trên card đồ họa.
Tóm lại bài viết này chủ yếu dành cho những người đã biết về con trỏ, nhưng có nhu cầu hiểu sâu hơn nữa, nên nếu bạn không hiểu thì cũng không sao hết nhé!