Wednesday, June 29, 2016

Ứng dụng của Pointer - Cấp phát động (Dynamic memory allocation)

Nhắc lại: Chúng ta đã biết con trỏ được tạo ra nhằm mục đích hỗ trợ tăng hiệu suất sử dụng bộ nhớ, trong compiler time chúng ta chỉ phải cấp một lượng bộ nhớ tối thiểu để chương trình có thể hoạt đông được, trong rum time nếu có nhu cầu sử dụng thêm chúng ta sẽ "xin" thêm một ít từ hệ điều hành.


1) Cấp phát và sử dụng vùng nhớ được cấp phát

C chuẩn cung cấp hàm malloc, một hàm thuộc trong thư viện stdio.h như một thủ tục cần thiết để "xin" bộ nhớ từ hệ điều hành, chức năng này được gọi là cấp phát động, tên tiếng anh là Dynamic allocation.

Cú pháp của hàm malloc:
       
(Kiểu dữ liệu trả về*)malloc(Số lượng ô nhớ muốn "xin" thêm);
VD1: Muốn xin thêm 200 ô nhớ kiểu int:
       
int *p_parameter
p_parameter = (int*)malloc(200);


Bây giờ hệ điều hành đã cấp cho pointer p_parameter toàn quyền sử dụng 200 ô nhớ kiểu int trong bộ nhớ và không một ai ngoài p_parameter có thể đụng chạm đến các ô nhớ này.

Thủ tục để "xin" thêm ô nhớ không có gì phức tạp đúng không nào, giờ tôi sẽ chỉ cho bạn cách sử dụng các ô nhớ vừa xin được. Chúng ta sẽ làm đièuu này thông qua hai bước:

B1: Gán giá trị cho các ô nhớ

Mặc định pointer sẽ trỏ vào địa chỉ ô nhớ đầu tiên trong vùng nhớ vừa được hệ điều hành cấp phát vậy p_parameter sẽ trỏ vào ô nhớ đầu tiên của vùng nhớ có 200 ô liên tiếp.

VD2: Gán giá trị 1001 cho ô nhớ đầu tiên của vùng nhớ được cấp phát:
       
int *p_parameter
p_parameter = (int*)malloc(200);
*p_parameter = 1001;

Vì p_parameter đã trỏ vào ô nhớ đầu tiên rồi nên bạn chỉ cần truyền giá trị vào bằng câu lệnh đơn giản *p_parameter = 1001.

VD3: Gán giá trị 10001 vào ô nhớ thứ hai của vùng nhớ.
       
int *p_parameter
p_parameter = (int*)malloc(200);
p_parameter ++;
*p_parameter = 10001;

Câu lệnh p_parameter ++; có nghĩa là tăng địa chỉ mà p_parameter đang trỏ vào lên một đơn vị, bây giờ thay vì trỏ vào ô nhớ đầu tiên nó sẽ trỏ vào ô nhớ thứ 2, việc truyền giá trị hoàn toàn tương tự như VD2.

B2: Truy xuất giá trị của các ô nhớ

Việc truy xuất hoàn toàn giống với việc gán giá trị, thay vì sử dụng câu lệnh gán thì bạn sẽ sử dụng câu lệnh đọc.
VD4: đọc giá trị các ô nhớ

       
#include "stdio.h"
#include "stdlib.h"

int main()
{
    int *p_parameter;
    p_parameter = (int*)malloc(200);
    *p_parameter = 1001;
    p_parameter ++;
    *p_parameter = 10001;
    printf("Gia tri cua o nho thu hai la: %d \n\r", *p_parameter);
    printf("Gia tri cua o nho thu nhat la: %d \n\r", *(--p_parameter));

    return 0;
}


câu lệnh *(--p_parameter) là để đưa con trỏ p_parameter về lại vị trí đầu tiên của vùng nhớ.

2) Giải phóng vùng nhớ được cấp phát

Bạn đã biết cách xin thêm ô nhớ và cũng đã biết cách để sử dụng chúng.
Câu hỏi bây giờ là "Nếu tôi không có nhu cầu sử dụng các ô nhớ đó nữa và để tránh lãng phí, tôi muốn trả chúng lại cho hệ điều hành thì phải làm sao?"

Câu trả lời "tôi phải free các ô nhớ đó đi", để giải phóng các ô nhớ đã được cấp phát tôi dùng hàm free(), free() là một hàm chuẩn được cung cấp bởi C và cũng nằm trong thư viện stdio.h được dùng để giải phóng các ô nhớ được cấp phát bởi malloc().

VD5: Giải phóng 200 ô nhớ được cấp phát trong VD1
       
free(p_parameter);

Sau câu lệnh này thì p_parameter không có quyền hành gì với 200 ô nhớ đó nữa, và hệ điều hành sẽ sử dụng nó cho mục đích khác.

Tip:
Nhiều lúc lập trình viên dù đã free() các ô nhớ nhưng sau đó vẫn quay trở lại truy xuất giá trị của các ô nhớ đó, đây là một lỗi cực kỳ nghiêm trọng vì khi bạn đã free() các ô nhớ  thì nó sẽ được sử dụng cho một mục đích khác và giá trị sẽ bị thay đổi.

Do đó, khi bạn truy xuất và đọc lại các ô nhớ đó thì giá trị bạn nhận được là giá trị đã bị thay đổi chứ không phải là giá trị bạn đã gán trước đó.

Lỗi này compiler không phát hiện được, bạn chỉ có thể biết được khi bạn chạy chương trình và nhận thấy kết quả không đúng. Trong trường hợp chương trình của bạn có cả ngàn dòng code thì một lỗi như thế này cực kỳ khó Debug.

Vậy nên, cách tốt nhất để tránh lỗi này là gán giá trị NULL cho pointer sau khi free(), trong C một NULL pointer không thể được sử dụng để truy xuất giá trị của ô nhớ, do đó nếu bạn cố gắng truy xuất giá trị ô nhớ sau khi đã free thì compiler sẽ báo lỗi, giúp bạn dễ dàng phát hiện và chỉnh sửa.

Cách free() một con trỏ an toàn tránh các lỗi tiềm ẩn
       
free(p_parameter);
p_parameter = NULL;


Hết ^^

No comments:

Post a Comment