Cơ Bản về File Pointer trong C

Trung Quân

File pointer trong C là một trong những khái niệm mà nhiều developer thấy khó hiểu. Tâm lý của đa số các bạn sau khi tìm hiểu một thời gian mà vẫn không hiểu rõ về file pointer thì bỏ cuộc, cũng dễ hiểu vì khó quá thì ta bỏ qua : ).

Tuy nhiên nếu bạn không muốn là một trong số này thì đây là dấu hiệu tốt vì bạn là một developer có tâm lý không chịu buông xuôi trước vấn đề gặp phải. Điều này rất tốt không chỉ trong lập trình mà trong cả khi cua gái, rất tốt hãy tiếp tục đọc vì
cũng như bạn tôi sẽ cố gắng giải thích đề bất cứ ai đã vô trang này rồi thì sẽ nắm vững về file pointer trong C, còn nếu mà đọc xong không hiểu thì hãy comment ở dưới nhé, tôi sẽ sẵn sàng giúp bạn giải đáp thắc mắc nếu có.

Đầu tiên tôi muốn bạn hiểu một khái niệm có vẻ như rất cơ bản trong lập trình đó là biến. Đừng nghĩ rằng biết rồi thì bỏ qua, vì một sự thật khiến tôi khá ngạc nhiên đó là rất nhiều bạn lập trình viên hạng ưu chưa hiểu hết về khái niệm biến.

Biến Là Gì

Hầu hết các bạn khi gặp câu hỏi này có thể nói vanh vách: Biến được sử dụng để lưu trữ tạm thời dữ liệu trong chương trình và giá trị của biến có thể thay đổi. Cách hiểu trên hoàn toàn đúng nhưng nếu chỉ có vậy thì bạn mới chỉ nhìn thấy bề nổi của tảng băng.
Ok vậy bây giờ chúng ta cùng nhau tìm hiểu sau hơn về khái niệm biến trong lập trình.

Bây giờ bạn mở text editor và tạo một file với tên là pointer.c với nội dung như sau:

#include <stdio.h>

int main(void)
{
    int aNumber = 100;
}

Đoạn code trên khởi tạo biến aNumber với kiểu dữ liệu là integer hay int. Trong chương trình khi một biến được khai báo (hoặc khởi tạo) thì máy tính sẽ sử dụng một vị trí trên bộ nhớ tạm (RAM) để thực hiện việc lưu trữ giá trị của biến này. Với điều kiện vị trí này
đang trống, tức là đang không được sử dụng.

Vị trí của vùng trống trên bộ nhớ trên còn được gọi là memory address. Và như vậy tên biến được dùng để đại diện cho một vị trí trên bộ nhớ. Khi khai báo hoặc khởi tạo biến, máy tính (hay cụ thể hơn đối với ngôn ngữ C, là chương trình thông dịch compiler trên máy tính)
sẽ quan tâm tới hai điều là tên biếnkiểu dữ liệu của biến. Bạn hình dung cấu trúc của bộ nhớ máy tính bao gồm các ô nằm liền kề nhau.

Đối với ví dụ trên khi khai báo biến int aNumber thì tên biến aNumber sẽ được gán với một địa chỉ trên bộ nhớ tạm. Và với kiểu dữ liệu là int thì compiler sẽ biết rằng cần phải lấy ra bao nhiêu đơn vị bộ nhớ từ vị trí trên bộ nhớ vừa được gán. Trong trường hợp kiểu dữ liệu là int thì sẽ là 4 byte hoặc 8 byte tùy vào loại CPU của máy.

Bây giờ bạn sửa đoạn code ở trên giống như sau:

#include <stdio.h>
int main(void)
{
    int aNumber = 100;
    printf("%p\n", &aNumber);
}

Ở trên bạn lưu ý phép toán & được sử dụng để lấy ra địa chỉ trên bộ nhớ mà biến aNumber trỏ tới. Ngoài ra ký tự %p được dùng để in giá trị của địa chỉ bộ nhớ. Chúng ta sẽ tìm hiểu kỹ hơn về &%p trong phần dưới đây. Nếu bạn compile chương trình trên (ở dưới đây tôi sử dụng chương trình gcc trên terminal của Linux):

$ gcc pointer.c -o pointer.out

Và chạy:

$ ./pointer.out

Thì bạn sẽ thấy kết quả tương tự như sau:

0x7fffe57e6864

Lưu ý rằng việc gán địa chỉ trên bộ nhớ cho biến aNumber sẽ khác nhau ở mỗi lần chạy chương trình. Nếu bạn chạy lần thứ 2, kết quả sẽ ra khác với lần đầu tiên.

Hai điều quan trọng về biến đó là địa chỉ vị trí trên bộ nhớ (vị trí bắt đầu) và dung lượng cần sử dụng để lưu trữ dữ liệu (vị trí kết thúc).

Như vậy bạn có thể thấy biến về thực chất được dùng để trỏ tới một vị trí trên bộ nhớ. Và giá trị 0x7fffe57e6864 ở trên được gọi là giá trị pointer của biến aNumber.

Tại Sao Sử Dụng Pointer

Nếu không sử dụng pointer thì khi bạn gọi một hàm test được khai báo với 1 tham số đầu vào như sau:

#include <stdio.h>
int main(void) {
    int a = 5;
    int function increaseNumber(int num) {
        num = num++;
        return num;
        // return ++num; // same same
    }

    printf("Return value from function: %i\n", increaseNumber(a));

    printf("Value of a: %i\n", a);
}

Trong trường hợp này biến a được dùng làm đối số truyền vào hàm increaseNumber() trên. Khi đó C sẽ copy giá trị của biến a ra bộ nhớ trước khi truyền giá trị này vào hàm.

Chính vì lý do trên nên giá trị của a sau khi gọi hàm increaseNumber() vẫn không đổi. Việc gọi hàm như vậy còn được gọi là call by value.

Tuy nhiên cách làm trên sẽ gặp phải vấn đề khi chúng ta gọi một hàm có đối số truyền vào là một biến và biến này là một mảng chứa 500 phần tử (500 phần tử). Điều này sẽ khiến số lượng bộ nhớ cần sử dụng tăng lên đáng kể.

Nếu sử dụng pointer trong trường hợp này bạn chỉ cần truyền vào giá trị của pointer của biến điều này sẽ hạn chế số lượng bộ nhớ cần sử dụng.

Sử Dụng Pointer

Bây giờ chúng ta sẽ tham khảo một đoạn code để lấy ra giá trị pointer (memory address) của biến a. Trước khi đi vào đọc code bạn cần lưu ý một số điểm sau:

  • Để khai báo một biến (ví dụ aPointer) là một pointer của biến khác bạn cần sử dụng ký tự * trước tên biến (ví dụ int *aPointer).
  • Để lấy giá trị pointer của biến chúng ta sử dụng ký tự & trước tên biến (ví dụ &a).
#include <stdio.h>
int main(void) {
    int a = 5;
    int *aPointer; // khai báo "aPointer" là một pointer

    aPointer = &a; // gán giá trị cho "aPointer" để trỏ tới biến "a"
    printf("aPointer: %p\n", aPointer); // in ra giá trị của pointer
}

Ở trên bạn chú ý khi in ra màn hình giá trị của pointer chúng ta sử dụng %p để thể hiện aPointer là một pointer.

Ngoài ra, khi khai báo pointer để trỏ tới một biến với kiểu dữ liệu int cần được định nghĩa với kiểu dữ liệu là int, tương tự cho các kiểu dữ liệu khác. Nếu bạn không biết trước pointer
sẽ trở tới biến với kiểu dữ liệu như thế nào bạn cần sử dụng cú pháp void *unknownPointer.

Dereference Pointer

Để lấy giá trị của biến một pointer trở tới chúng ta sử dụng toán tử dereference pointer hay * (tương tự như khi khai báo pointer). Ví dụ:

#include <stdio.h>
int main(void) {
    int a = 5;
    int *aPointer; // khai báo "aPointer" là một pointer

    aPointer = &a; // gán giá trị cho "aPointer" để trỏ tới biến "a"
    printf("Dereference aPointer value: %i\n", *aPointer);
}

Khi chạy đoạn code trên bạn sẽ thấy kết quả hiện ra như sau:

Dereference aPointer value: 5

Điều này xảy ra là bởi vì aPointer là pointer trỏ tới địa chỉ bộ nhớ chứa giá trị của biến a, khi derefrence pointer này *aPointer bạn sẽ có được giá trị của biến ở vị trí bộ nhớ
aPointer trỏ tới hay giá trị của biến a hay 5.

Bây giờ bạn tiếp tục update đoạn code trong pointer.c thành nội dung như sau:

#include <stdio.h>
int main(void) {
    int a = 5;
    int *aPointer; // khai báo "aPointer" là một pointer

    aPointer = &a; // gán giá trị cho "aPointer" để trỏ tới biến "a"

    *aPointer = 10; // sử dụng dereference pointer để thay đổi giá trị của "a"
    printf("Value of a: %i\n", *aPointer);
}

Khi compile và chạy bạn sẽ nhận được giá trị:

Value of a: 10

Điều này xảy ra là bởi vì bạn đã sử dụng dereference pointer để thay đổi giá trị của biến a bằng câu lệnh:

*aPointer = 10;

Tới đây chúng ta đã tìm hiểu một trong những khái niệm quan trọng nhất của ngôn ngữ lập trình C là pointer. Nếu bạn có thêm thắc mắc về pointer trong C vui lòng để lại comment phía dưới.

1 Phản Hồi

Thêm Phản Hồi

Bài Viết Liên Quan