Sự thật về con trỏ trong ngôn ngữ C – “Cơ bản” nhưng không “đơn giản”

23:18 26/10/2023

Con trỏ là một trong những điều cơ bản nhất cần hiểu trong ngôn ngữ C. Theo định nghĩa thì con trỏ cũng là biến nhưng nó không lưu giá trị bình thường, mà là biến trỏ tới 1 địa chỉ khác, tức mang giá trị là 1 địa chỉ. Nhưng nếu con trỏ chỉ là địa chỉ thì tại sao chúng khó hiểu?

Bởi vì chúng là một dạng  gián tiếp và nếu không cẩn thận, bạn có thể “bị lạc” với các khái niệm về con trỏ trong bộ nhớ.

Tips để học cách sử dụng con trỏ C là hãy thực hiện một cách chậm rãi. Con trỏ là một ý tưởng đơn giản nhưng bạn cần dành thời gian và tìm hiểu mọi thứ. Để hiểu con trỏ là gì, bạn cần đi sâu vào bộ nhớ của máy tính.

Mỗi khi bạn khai báo một biến, máy tính sẽ tạo một khoảng trống cho biến đó trong bộ nhớ. Nếu bạn khai báo một biến bên trong một hàm như main(), máy tính sẽ lưu trữ biến đó vào một phần bộ nhớ gọi là stack. Nếu một biến được khai báo bên ngoài bất kỳ hàm nào, nó sẽ được lưu trữ trong phần toàn cục của bộ nhớ gọi là globals.

Ví dụ máy tính phân bổ vị trí bộ nhớ 4.100.000 trong stack cho biến x. Nếu bạn gán số 4 cho biến thì máy tính sẽ lưu số 4 ở vị trí 4.100.000. Nếu bạn muốn tìm địa chỉ bộ nhớ của biến, bạn có thể sử dụng toán tử &:

Câu lệnh in ra cho biết biến x được lưu ở vị trí 0060FF24 trong bộ nhớ.

Địa chỉ của biến cho bạn biết nơi tìm biến trong bộ nhớ. Đó là lý do tại sao địa chỉ còn được gọi là con trỏ, vì nó trỏ tới biến trong bộ nhớ.

Một ví dụ về con trỏ: 

Hãy tưởng tượng bạn cần viết một game để định hướng di chuyển trên vùng biển. Trò chơi sẽ cần phải kiểm soát nhiều thứ, như điểm số, mạng sống cũng như vị trí hiện tại của người chơi. Bạn sẽ không muốn viết trò chơi dưới dạng một đoạn mã lớn trong hàm main(), thay vào đó, bạn sẽ tạo nhiều hàm nhỏ hơn, mỗi hàm sẽ thực hiện một số chức năng hữu ích trong trò chơi.

Hãy bắt đầu viết mã mà không cần lo lắng về con trỏ và bạn sẽ chỉ sử dụng các biến như bạn vẫn thường làm. Trong ví dụ này bạn sẽ chỉ điều hướng con tàu trên biển.

Trò chơi sẽ theo dõi vị trí của người chơi bằng vĩ độ và kinh độ. Vĩ độ là khoảng cách về phía bắc hoặc phía nam, kinh độ là vị trí của người chơi ở phía đông hoặc phía tây. Nếu người chơi muốn đi về phía đông nam, điều đó có nghĩa là vĩ độ của họ sẽ giảm xuống và kinh độ sẽ tăng lên:

Vì vậy, bạn có thể viết hàm go_south_east() nhận các đối số cho vĩ độ (latitude) và kinh độ (longitude), sau đó nó sẽ cùng tăng và giảm:

Chương trình khởi động một con tàu tại vị trí [32, –64], vì vậy nếu nó di chuyển về phía đông nam, vị trí mới của tàu sẽ phải là [31, –63]. Nhưng nếu bạn biên dịch và chạy chương trình thì giá trị của kinh độ và vĩ độ không hề thay đổi: 

Nguyên nhân là do cách C gọi các hàm và C gửi đối số dưới dạng giá trị. Ban đầu, hàm main() có một biến cục bộ gọi là longitude có giá trị 32. 

Khi máy tính gọi hàm go_south_east(), nó sẽ sao chép giá trị của biến longitude vào đối số longg. Đây chỉ là phép gán từ biến longitude sang biến longg. Khi gọi một hàm, bạn không gửi biến làm đối số mà chỉ gửi giá trị của nó.

Khi hàm go_south_east() thay đổi giá trị của longg, hàm này chỉ thay đổi bản sao cục bộ của nó. Điều đó có nghĩa là khi máy tính quay về hàm main() thì biến longitude vẫn có giá trị ban đầu là 32. 

Nhưng nếu đó là cách C hoạt động thì làm sao bạn có thể viết một chức năng cập nhật một biến? Bạn hãy thử truyền một con trỏ tới biến đó. 

Thay vì truyền giá trị của các biến latitudelongitude, điều gì sẽ xảy ra nếu bạn truyền địa chỉ của chúng? Nếu biến kinh độ tồn tại trong bộ nhớ stack ở vị trí 4.100.000, điều gì sẽ xảy ra nếu bạn truyền số vị trí 4.100.000 làm tham số cho hàm go_south_east()?

Nếu hàm go_south_east() được thông báo rằng giá trị vĩ độ tồn tại ở vị trí 4.100.000 thì nó không chỉ có thể tìm thấy giá trị latitude hiện tại, mà còn có thể thay đổi nội dung của biến latitude ban đầu. Tất cả chức năng cần làm là đọc và cập nhật nội dung của vị trí bộ nhớ 4.100.000.

Vì hàm go_south_east() đang cập nhật biến latitude ban đầu nên máy tính sẽ có thể in ra vị trí đã cập nhật khi quay lại hàm main().

Bây giờ bạn đã biết lý thuyết về việc sử dụng con trỏ để sửa hàm go_south_east(), đã đến lúc xem chi tiết về cách bạn thực hiện.

Có ba điều bạn cần biết để sử dụng con trỏ để đọc và ghi dữ liệu.

  1. Lấy địa chỉ của một biến

Bạn đã thấy rằng bạn có thể tìm thấy nơi một biến được lưu trữ trong bộ nhớ bằng toán tử &:

Format %p sẽ in ra vị trí ở định dạng Hex (cơ sở 16).

Nhưng khi bạn đã có địa chỉ của một biến, bạn có thể muốn lưu nó ở đâu đó. Để làm điều này, bạn sẽ cần một biến con trỏ. Biến con trỏ chỉ là biến lưu trữ địa chỉ bộ nhớ. Khi khai báo một biến con trỏ, bạn cần cho biết loại dữ liệu nào được lưu tại địa chỉ mà nó sẽ trỏ tới:

2. Đọc nội dung của một địa chỉ.

Khi bạn có địa chỉ bộ nhớ, bạn sẽ muốn đọc dữ liệu được lưu trữ ở đó.  Bạn làm điều này với toán tử *:

Các toán tử * và & là đối lập nhau. Toán tử & lấy một phần dữ liệu và cho bạn biết nơi nó được lưu trữ. Toán tử * lấy một địa chỉ và cho bạn biết những gì được lưu trữ ở đó.

3. Thay đổi nội dung của một địa chỉ.

Nếu bạn có một biến con trỏ và muốn thay đổi dữ liệu tại địa chỉ nơi biến đó trỏ đến, bạn chỉ cần sử dụng lại toán tử *. Nhưng lần này bạn cần sử dụng nó ở vế trái của biểu thức:

Bây giờ bạn đã biết đọc và viết nội dung của một vị trí bộ nhớ, đã đến lúc để sửa hàm go_south_east(). 

Vậy là chương trình sẽ in ra màn hình giá trị: 

Tổng kết lại, chúng ta sẽ cần nắm được các kiến thức: 

  • Các biến được phân bổ lưu trữ trong bộ nhớ.
  • Biến cục bộ tồn tại trong stack.
  • Biến toàn cục nằm trong phần globals.
  • Con trỏ chỉ là biến lưu trữ địa chỉ bộ nhớ.
  • Toán tử & tìm địa chỉ của một biến.
  • Toán tử * có thể đọc nội dung của địa chỉ bộ nhớ.
  • Toán tử * cũng có thể thay đổi nội dung của địa chỉ bộ nhớ.

Hy vọng những hướng dẫn trong bài viết này sẽ giúp bạn hiểu hơn về con trỏ trong ngôn ngữ C và hiểu thêm về các biến của nó. Hãy thực hành thật chậm và chắc chắn để hiểu về con trỏ nhé!

Bộ môn Ứng dụng phần mềm
Trường Cao đẳng FPT Mạng cá cược bóng đá cơ sở Hà Nội

Cùng chuyên mục

Đăng Kí học Fpoly 2023