0

One Pointer

Mở đầu

1. Hiểu về con trỏ

2. Con trỏ tới biến

3. Mảng con trỏ *ptr[]

a. Khái niệm

Mỗi phần tử của mảng đều là một con trỏ, và có thể lưu trữ địa chỉ của hàm, chuỗi bất cứ ở đâu được cấp phát trong RAM.

int *arr[5]; // Mảng này có 5 phần tử là 5 con trỏ int.

b. Ứng dụng

Lưu trữ nhiều chuỗi kí tự

#include <stdio.h>


int main() {
  char *fruits[] = {"Apple", "Banana", "Cherry"};

  printf("dia chi cua (fruits + 0) = %p\n", (fruits + 0)); // &fruits[0]
  printf("dia chi cua (fruits + 1) = %p\n", (fruits + 1)); // &fruits[1]
  printf("dia chi cua (fruits + 2) = %p\n", (fruits + 2)); // &fruits[2]
  
  
  printf("gia trị tại dia chi (fruits + 0) = %p\n", *(fruits + 0)); // fruits[0]
  printf("gia trị tại dia chi (fruits + 1) = %p\n", *(fruits + 1)); // fruits[1]
  printf("gia trị tại dia chi (fruits + 2) = %p\n", *(fruits + 2)); // fruits[2]

  
  // các phần tử
  printf("địa chỉ cua A = %p\n", *(fruits + 0) + 0);      // &fruits[0][0]
  printf("giá trị cua A = %c\n", *(*(fruits + 0) + 0));   // fruits[0][0]
  printf("địa chỉ cua p = %p\n", *(fruits + 0) + 1);      // &fruits[0][1]
  printf("giá trị cua p = %c\n", *(*(fruits + 0) + 1));   // fruits[0][1]
  
  // Tươnh tự
  
}

Output

dia chi cua (fruits + 0) = 0x7ffdd2716df0
dia chi cua (fruits + 1) = 0x7ffdd2716df8
dia chi cua (fruits + 2) = 0x7ffdd2716e00
gia trị tại dia chi (fruits + 0) = 0x5635ea3e9008
gia trị tại dia chi (fruits + 1) = 0x5635ea3e900e
gia trị tại dia chi (fruits + 2) = 0x5635ea3e9015
địa chỉ cua A = 0x5635ea3e9008
giá trị cua A = A
địa chỉ cua p = 0x5635ea3e9009
giá trị cua p = p

image.png

Note: Hãy chú ý khoảng cách giữa 2 địa chỉ, bạn có thể hiểu thêm khi suy nghĩ về nó: "Tại sao nó lại có số lượng byte như vậy?"

Điều này có khác biệt so với khi ta sử dụng char arr[][] - mảng 2D kiểu char để lưu trữ nhiều chuỗi.

  • Khi dùng theo cách này thì mỗi chuỗi phải yêu cầu kích thước cố định -> Điều này gây ra lẫn phí bộ nhớ nếu không dùng hết cho mỗi chuỗi.

Còn char *arr[] chỉ lưu con trỏ đến string, tối ưu bộ nhớ.

Quản lí mảng 2D có độ dài khác nhau

#include <stdio.h>

int main() {
    int row1[] = {1, 2, 3};
    int row2[] = {4, 5};
    int row3[] = {6, 7, 8, 9};

    int *arr[] = {row1, row2, row3};  // mỗi phần tử là 1 con trỏ

    printf("%d\n", arr[0][2]); // 3
    printf("%d\n", arr[1][1]); // 5
    printf("%d\n", arr[2][3]); // 9
}

Callback table / Function pointer table

Trong embedded, hay dùng mảng con trỏ hàm để chọn hàm xử lý theo sự kiện.

#include <stdio.h>

void led_on()  { printf("LED ON\n"); }
void led_off() { printf("LED OFF\n"); }
void led_toggle() { printf("LED TOGGLE\n"); }

int main() {
    void (*actions[])() = {led_on, led_off, led_toggle};

    actions[0](); // gọi led_on
    actions[2](); // gọi led_toggle
}

Mỗi phần tử trong mảng là một con trỏ hàm.

4. Con trỏ tới mảng (*ptr)[]

a. Khái niệm

Cần phải phân biệt rõ 2 khái niệm: Con trỏ tới một mảng và mảng các con trỏ vì chúng có cách viết khá tương tự nhau nhưng khác 1 chút xíu.

  • Pointer to array: con trỏ sẽ trỏ tới cả một mảng (block liên tục).
int (*p)[5]; // con trỏ tới một mảng có 5 phần tử
  • Array of pointers: mỗi phần tử của mảng sẽ là một con trỏ
int *arr[5]; // arr: một mảng có 5 phần tử là con trỏ int.

Ví dụ

Trong ví dụ này, tôi sẽ tạo ra 2 kiểu con trỏ để so sánh sự khác nhau của nó:

  • int *p: Con trỏ mà nó trỏ đến phần tử đầu tiên của arr
  • int(*ptr)[5]: Con trỏ này sẽ trỏ đến toàn bộ mảng
#include <stdio.h>

int main() {
    int arr[5] = {1, 2, 3, 4, 5};

    // Tạo con trỏ integer
    int *p;

    // Tạo con trỏ tới một mảng
    int(*ptr)[5];

    // Trỏ tới phần tử 0 của arr
    p = arr;

    // Trỏ tới toàn bộ arr
    ptr = &arr;
  
    // Địa chỉ mà 2 con trỏ đang trỏ tới.
  	printf("p = %p\n", p);
  	printf("*ptr = %p\n\n", *ptr);
  
  	// Tăng con trỏ -> tăng đia chỉ
    p++;
    ptr++;
    
    // Địa chỉ và giá trị mà 2 con trỏ đang trỏ tới sau khi tăng.
    printf("p = %p\n", p);
    printf("*p = %d\n", *p);
    
    printf("ptr = %p\n", ptr);
  	printf("*ptr = %p\n", *ptr);

    return 0;
}

Output:

p = 0x7fff30a19ce0
*ptr = 0x7fff30a19ce0

p = 0x7fff30a19ce4
*p = 2
ptr = 0x7fff30a19cf4
*ptr = 0x7fff30a19cf4

Kết luận

  • (1): Địa chỉ ban đầu mà cả 2 con trỏ đang trỏ tới arr là như nhau 0x7fff30a19ce0. image.png

Tuy nhiên sau khi tăng hai con trỏ bằng

p++;
ptr++;

Địa chỉ p đang trỏ tới 0x7fff30a19ce4 cách nhau 4 byte so với địa chỉ phần tử đầu tiên.

Địa chỉ ptr đang trỏ tới 0x7fff30a19cf4 cách nhau 20 byte = size của arr[5] so với lúc chưa tăng con trỏ. image.png

Có thể xác thực điều này bằng cách in ra giá trị tại địa chỉ mà con trỏ *ptr(*ptr)[5] đang trỏ tới.

  • Trước khi tăng con trỏ
printf("*p = %d\n", *p);
printf("(*ptr)[0] = %d\n", (*ptr)[0]); // trỏ tới vị trỉ đâu tiên của block

sẽ được kết quả

*p = 1
(*ptr)[0] = 1
  • Sau khi tăng con trỏ, sẽ được kết quả
*p = 2
(*ptr)[0] = 32766 // giá trị rác trong máy tính
  • (2): Giá trị của *ptrptr đều như nhau (đều là địa chỉ).

b. Ứng dụng

Quản lí mảng 2 chiều

Code mẫu

#include <stdio.h>

int main()
{
  // hàng x cột
  int arr_2D[2][3] = {
    {1, 2, 3},
    {4, 5, 6}
  };
  
  printf("Đia chỉ của arr_2D[0][0] = %p\n", &arr_2D[0][0]);
  printf("Đia chỉ của arr_2D[1][0] = %p\n", &arr_2D[1][0]);
  
  
  // Tạo 1 con trỏ tới mảng để quản lí mảng 2 chiều
  int (*ptr_arr_2D)[3] = arr_2D;
  
  // Quản li hàng [0] bằng cách trỏ tới địa chỉ của arr_2D[0][0]
  printf("(*ptr_arr_2D)[3] = %p\n", ptr_arr_2D);
  
  printf("\nLấy phần tử ở hàng 0 của arr_2D thông qua ptr_arr_2D\n");
  printf("arr_2D[0][0] = %d\n", (*ptr_arr_2D)[0]);
  printf("arr_2D[0][1] = %d\n", (*ptr_arr_2D)[1]);
  printf("arr_2D[0][2] = %d\n", (*ptr_arr_2D)[2]);
  
  // Quản li hàng [1] bằng cách trỏ tới địa chỉ của arr_2D[1][0]
  ptr_arr_2D++;
  printf("\n(*ptr_arr_2D)[3]= %p sau khi tăng\n", ptr_arr_2D);
  printf("Lấy phần tử ở hàng 1 của arr_2D thông qua ptr_arr_2D\n");
  printf("arr_2D[1][0] = %d\n", (*ptr_arr_2D)[0]);
  printf("arr_2D[1][1] = %d\n", (*ptr_arr_2D)[1]);
  printf("arr_2D[1][2] = %d\n", (*ptr_arr_2D)[2]);
  
  return 0;
}

Output

Đia chỉ của arr_2D[0][0] = 0x7ffea704d410
Đia chỉ của arr_2D[1][0] = 0x7ffea704d41c
(*ptr_arr_2D)[3] = 0x7ffea704d410

Lấy phần tử ở hàng 0 của arr_2D thông qua ptr_arr_2D
arr_2D[0][0] = 1
arr_2D[0][1] = 2
arr_2D[0][2] = 3

(*ptr_arr_2D)[3]= 0x7ffea704d41c sau khi tăng
Lấy phần tử ở hàng 1 của arr_2D thông qua ptr_arr_2D
arr_2D[1][0] = 4
arr_2D[1][1] = 5
arr_2D[1][2] = 6

image.png

Mảng 2D sẽ được sắp xếp các byte liên tiếp trong memory của máy tính.

Quản lí memory có kích thước cố định

Ta có thể tạo các memory có kích thước cố định và dùng Pointer to Array.

Kết

  • Bài viết này chưa đầy đủ, đây chỉ là ghi chép tạm thời của tôi trong quá trình học, tôi sẽ bổ sung đầy đủ hơn trong tương lai gần.
  • Hy vọng qua bài viết này có thể giúp ích các bạn hiểu hơn về cách sử dụng con trỏ tới 1 mảng để quản lí memory.
  • Nếu bạn có thắc mắc gì hãy bình luận dưới bài viết.

All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí