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 ^^

Tuesday, May 5, 2015

Khai báo biến trong vòng lặp

Hôm nay là một chủ đề vừa cũ vừa mới, nhiều bạn chắc chắn sẽ bất ngờ khi biết điều này

Một biến khi được khai báo trong vòng lặp sẽ chỉ tồn tại cục bộ trong vòng lặp đó, nếu ra khỏi vòng lặp thì biến đó sẽ bị hủy.

Do thoi gian trong chuong trinh C

phai include time.h






clock_t begin, end;
double time_spent;

begin = clock();
/* here, do your time-consuming job */
end = clock();
time_spent = (double)(end - begin) / CLOCKS_PER_SEC;
thoi gian ket qua duoc tinh bang s

neu muon tinh bang ms thi lam nhu sau:

#include <time.h>
#include <stdio.h>

int main(){
    clock_t start = clock();
    // Execuatable code
    clock_t stop = clock();
    double elapsed = (double)(stop - start) * 1000.0 / CLOCKS_PER_SEC;
    printf("Time elapsed in ms: %f", elapsed);
}









Static variable

Giong nhu ten cua no, static co nghia la "tinh", no duoc the hien thong qua 3 phuong dien cua the sau:
1) global static variable:
chi co tac dung trong file no chua, k co tac dung o file khac, hay noi cach khac khong the dung extern voi bien static
2) static variable in function:
giu gia tri sau moi lan goi ham, co nghia la khi ham ket thuc thi gia tri cua bien khong bi mat di
3) static variable in loop:
tranh khoi tao lai gia tri cua variable, tuc la khi lap thi variable chi khoi tao 1 lan duy nhat va gia khong bi khoi tao lai trong cac lan lap tiep theo

Saturday, April 25, 2015

Khi nao dung malloc, khi nao dung calloc

calloc:  cap phat va khoi tao gia tri = 0 cho tat ca cac o nho
malloc: cap phat ma khong khoi tạo

su dung malloc nhanh hon
vi viec khoi tao se mat thoi gian, nen thong thuong trong cac he thong doi hoi toc do thi dung malloc, nhung doi luc viec khoi taola quan trong thi lua chon calloc.
su dung calloc an toan hon

Wednesday, April 22, 2015

Hiểu về Union [Understand about Union]

Union là gì?

Để đảm bảo an toàn dữ liệu, mỗi biến sau khi khai báo sẽ được cấp phát một vùng nhớ tách biệt với nhau, nhưng vì nhu cầu giải quyết các bài toán đa dạng, đôi lúc bạn sẽ muốn khai báo tất cả chúng "đè" lên nhau.
Union chính là cấu trúc hỗ trợ bạn thực hiện điều đó, bạn có thể khai báo nhiều biến với nhiều kiểu dữ liệu khác nhau trên cùng một vùng nhớ.

Khai báo Union như thế nào?

Khai báo union với cấu trúc như sau:
Hình 1: Cấu trúc khai báo kiểu union
Trong đó union là từ khóa bắt buộc, union tạo ra một kiểu dữ liệu riêng, và các biến thành phần cũng được truy xuất bằng giấu '.' tương tự như struct.
Hình 2: Khai báo và truy xuất giá trị của union
Trong Hình 2 chúng ta đã khai báo một kiểu union có tên là key, và trong hàm main khai báo biến My_key có kiểu dữ liệu là key, khởi tạo và truy xuất giá trị cho các biến thành phần của My_key.

Ghi "đè" nghĩa là sao?

Để xác minh việc union ghi "đè" các biến thành phần lên nhau ta sẽ chạy đoạn chương trình như sau:
Hình 3: đoạn chương trình kiểm tra kích thước của union
Kết quả là: 
Hình 4: Kết quả đoạn chương trình kiểm tra kích thước union
Ta thấy: Kích thước của key_name là 1 byte, key_number là 4 byte. 
Nếu key_name và key_number nằm ở hai vùng nhớ khác nhau thì union key phải có kích thước 5 byte mới chứa đủ 2 biến thành phần này.
Nhưng: Tổng kích thước của union không phải là 5 byte mà là 4byte, điều đó có nghĩa là key_name và key_number được ghi "đè" lên nhau chứ không phải tách rời nhau.

Tác dụng của việc ghi "đè"?

Bây giờ hãy quay lại ví dụ về union ở Hình 2, chạy đoạn chương trình đó và kết quả như sau:
Hình 3: Kết quả chạy chương trình Hình 2
Kết quả chỉ có giá trị của key_name là giống với giá trị khởi tạo ban đầu còn key_number thì không.
Câu hỏi: Vì sao kết quả chạy chương trình lại như thế?
Trả lời: Trong union biến thành phần nào được gán giá trị sau sẽ ghi đè lên những biến thành phần còn lại.

Như vậy, theo thứ tự câu lệnh trong đoạn chương trình Hình 2, ta thấy rằng biến key_number được gán giá trị trước, key_name được gán sau, do đó giá trị của key_name sẽ ghi đè lên giá trị của key_number và làm sai khác giá trị trước đó của key_number, dẫn tới kết quả chạy chương trình là key_name đúng, key_number sai.

Cơ chế ghi "đè" của union?

Nhưng: Chú ý kỹ hơn nữa ta sẽ thấy 86 chính là giá trị thập phân của ký tự in hoa 'V', vậy có mối liên hệ gì ở đây?
Để hiểu rõ vấn đề này chúng ta phải hiểu cách union "ghi đè" các biến lên bộ nhớ như thế nào
Hình 4: Vùng nhớ 4byte của union key
Khi thực hiện câu lệnh My_key.key_number = 19; giá tri hexa tương ứng 0x00000014 sẽ được ghi vào 4 byte nhớ, byte 0 có giá trị 0x14 và các byte còn lại có giá trị 0x00.
Đến câu lệnh My_key.key_name = 'V'; giá trị hexa 0x56 sẽ được ghi vào byte 0 và làm mất giá trị 0x14 trước đó.
Do đó khi truy xuất My_key.key_number giá trị đọc được sẽ là 0x00000056 = 86 chứ không phải là 0x00000014 = 19 như ban đầu nữa, điều này giải thích cho kết quả chương trình.

Vậy: Union sẽ ghi giá trị các biến thành phần vào các vị trí ô nhớ của nó từ byte thấp đến byte cao, biến ghi sau sẽ đè lên biến ghi trước, key_name chỉ có dung lượng 1 byte nên giá trị của nó sẽ được ghi vào byte 0, key_number có dung lượng 4 byte do đó giá trị của nó sẽ được ghi vào cả 4 ô nhớ theo thứ tự từ byte thấp (byte 0) đến byte cao (byte 4).

Hôm nay vậy là đủ, chúc các bạn buổi tối vui vẻ ^^, hẹn gặp lại trong các bài viết tiếp theo.


Struct

Mảng (array) trong C cho phép bạn khai báo một chuỗi liên tiếp các phần tử có cùng kiểu dữ liệu tên giống nhau, mỗi phần tử trong mảng được truy xuất dựa vào đối số vị trí của nó.
Nhưng nếu bạn muốn khai báo một mảng với nhiều phần tử có kiểu dữ liệu khác nhau, tên mỗi phần tử là tùy ý và truy xuất các phần tử dựa vào tên của chúng .