Tuesday, April 26, 2011

Tham chiếu trong C

Tham chiếu

Trong lập trình C sẽ có nhiều lúc bạn muốn xem địa chỉ của một biến, lúc này bạn chỉ cần thêm ký tự & trước tên biến để đọc địa chỉ nó.
Hình 1: Mô tả bộ nhớ được sử dụng trong chương trình C
Hãy hình dung mộ bộ nhớ có 16 ô nhớ được đánh địa chỉ tương ứng từ 0 đến 15, mỗi ô nhớ đều có giá trị ban đầu khác nhau, tôi biễu diễn bằng 16 màu sắc khác nhau.
Trong một chương trình C, khi tôi khai báo một biến kiểu char như sau:
char mau_sac;
mau_sac = 50;
Hệ điều hành sẽ cấp phát 1 ô nhớ chưa được sử dụng trên RAM cho biến mau_sac, giả sử ô nhớ thứ 5 (Hình 1) được cấp cho biến mau_sac. Việc cấp phát này do hệ điều hành thực hiện và bạn không hề hay biết.
Nếu muốn biết ô nhớ nào được cấp cho biết mau_sac bạn phải sử dụng tham chiếu, xem câu lệnh sau:
printf("o nho tai dia chi %d co gia tri %s\n",&mau_sac,mau_sac);
kết qủa in ra:
o nho tai dia chi 5 co gia tri 50
Vậy sử dụng ký tự & trước tên biến có thể giúp ta đọc được địa chỉ của biến trên bộ nhớ, kỹ thuật này gọi là tham chiếu.
Chú ý: Đối với mảng dạng tham chiếu của mảng là tên mảng mà không có ký tự &

int array[1024];
printf("Dia chi cua array: 0x%x",array);
// tham chiếu của mảng int array[1024] là array chứ không phải &array

Ứng dụng tham chiếu

1/ Tránh bay hơi: bạn đã biết rằng khi truyền một đối số vào Function thì đối số sẽ được copy vào Stack, các tính toán trên đối số sẽ được thực hiện trên stack, vậy nên sẽ xảy ra bay hơi giá trị, tức là giá trị tính toán trên đối số sẽ mất sau khi thoát ra khỏi Function.

Trong trường hợp này thay vì truyền một biến thì ta truyền địa chỉ (tham chiếu) của biến vào hàm, khi đó các tính toán sẽ được thực hiện và lưu lại tại vị trí thực của biến trên RAM chứ không phải tại vị trí copy trên Stack, do đó giá trị sau tính toán không bị bay hơi.

Lưu ý: Để sử truyền được tham chiếu của biến vào hàm ta cần khai báo biến đối số kiểu con trỏ, xem so sánh hai đoạn chương trình sau:


#include "stdio.h"

int my_double (int a)
{
    a = 2*a;
    return a;
}

int main ( void ){
    int val = 10;
    my_double(val);
    printf("val = %d\n\r",val);
return 0;
}
                                                        Source 1: Không dùng tham chiếu


#include "stdio.h"

int my_double (int *a)
{
    *a = 2*(*a);
    return *a;
}

int main ( void ){
    int val = 10;
    my_double(&val);
    printf("val = %d\n\r",val);
return 0;
}
                                                              Source 2: Dùng tham chiếu

Kết qủa in ra của hai đoạn chương trình trên là khác nhau, đoạn chương trình 1 biến val sẽ được copy vào stack và tính toán trên stack do đó giá trị của val không thay đổi sau khi gọi hàm my_double.
ngược lại ở đoạn chương trình 2 các tính toán trong hàm double được thực hiện trực tiếp tại vị trí ô nhớ của biến val trên RAM do đó giá trị val thay đổi sau khi gọi hàm my_double.

// Kết quả in ra của Source 1
val = 10

// Kết quả in ra của Source 2
val = 20
2/ Tránh tràn stack: khi một Funcion cần các biến có kích thước quá lớn
Ta hạn chế khai báo các biến cục bộ có dung lượng lớn (VD: int array[1024]) vì nó có thể làm tràn stack, trường hợp này ta có thể khai báo một biến toàn cục tương đương và truyền nó vào hàm dưới dạng tham chiếu, khi đó sẽ không ảnh hưởng đến dung lượng stack.

#include "stdio.h"

int my_array[1024];
void my_function (int array[])
{
 // code here
}

int main ( void ){
my_function(my_array);
return 0;
}
                                                   Source 3: Sử dụng tham chiếu tránh tràn stack


#include "stdio.h"

void my_function (void)
{
 int my_array[1024];
 // code here
}

int main ( void ){
my_function();
return 0;
}
                                            Source 4: Không sử dụng tham chiếu, có nguy cơ tràn stack

Trong đoạn chương trình 3 và 4 đều cần sử dụng mảng my_array[1024], nhưng viết theo cách của source 3 sẽ an toàn hơn vì nó k gây tràn stack.


No comments:

Post a Comment