- vừa được xem lúc

Con trỏ trong C

0 0 2

Người đăng: Nguyễn Đặng Triều

Theo Viblo Asia

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.

Bình luận

Bài viết tương tự

- vừa được xem lúc

Memory layout của một chương trình C/C++

1. Đặt Vấn Đề.

0 0 54

- vừa được xem lúc

Sự khác nhau giữa bộ nhớ Heap và bộ nhớ Stack trong lập trình

1. Giới thiệu. . Như chúng ta đã biết thì việc Quản lý bộ nhớ đối với một lập trình viên là rất quan trọng.

0 0 67

- vừa được xem lúc

Tích hợp thư viện C/C++ vào một dự án Flutter như thế nào?

Chào các bạn hôm nay rảnh rỗi thì mình viết một bài hướng dẫn về cách tích hợp một thư viện C/C++ vào một dự án Flutter. Thì cũng khá đơn giãn thôi vì Flutter đã cũng cấp cho chúng ta một thư viện khá

0 0 71

- vừa được xem lúc

Programming

Definition of Programming. .

0 0 48

- vừa được xem lúc

Gọi hàm của C++ trong Python bằng ctypes

Nếu đã từng dùng qua các thư viện liên quan đến toán học của Python như numpy, các bạn sẽ để ý thấy các nó có tốc độ xử lý rất nhanh. Điều này là do một phần của package này được viết bằng C/C++ và ph

0 0 49

- vừa được xem lúc

Build ứng dụng Cross Platform với Go và CGO

1. Giới thiệu.

0 0 47