1. What are pointer?
Pointer
A pointer is a dynamic variable, whose value is the address of another variable, i.e., direct address of the memory location. Like any variable or constant, you must declare a pointer before you can use it to store any variable address.
Pascal - Pointer arithmetic
Pascal - Array of pointers
Pascal - Pointer to pointer
Passing pointers to subprograms in Pascal
Return pointer from subprograms in Pascal
Ref
http://www.tutorialspoint.com/pascal/pascal_pointer_arithmetic.htm
Printing a Memory Address in Pascal
In Pascal, we can assign the address of a variable to a pointer variable using the address operator (@). We use this pointer to manipulate and access the data item. However, if for some reason, we need to work with the memory address itself, we need to store it in a word type variable.NIL Pointer
It is always a good practice to assign a NIL value to a pointer variable in case you do not have exact address to be assigned. This is done at the time of variable declaration. A pointer that is assigned NIL points to nowhere.Pascal Pointers in Detail
Pointers have many but easy concepts and they are very important to Pascal programming. There are following few important pointer concepts, which should be clear to a Pascal programmerPascal - Pointer arithmetic
Pascal - Array of pointers
Pascal - Pointer to pointer
Passing pointers to subprograms in Pascal
Return pointer from subprograms in Pascal
Ref
http://www.tutorialspoint.com/pascal/pascal_pointer_arithmetic.htm
Để hiểu rõ về con trỏ thì mới các bạn làm quen với các khái niệm: Bộ nhớ ảo, địa chỉ ảo trước nhé bạn!
2. Virtual Memory - Bộ nhớ ảo:
Quản lý bộ nhớ vật lý (cấp phát, thu hồi) là một vấn đề cực kì phức tạp trong hệ thống máy tính, để bảo đảm sự hiệu quả, đúng đắn, an toàn cho việc quản lý đó, hệ điều hành xây dựng lên các vùng nhớ ảo.
Trong hệ thống máy tính, bộ nhớ ảo (virtual memory) là một kĩ thuật cho phép một chương trình ứng dụng tưởng rằng mình đang có một dải bộ nhớ liên tục (một không gian địa chỉ), - các hộp tủ liên tục
Trong khi thực ra phần bộ nhớ này có thể bị phân mảnh trong bộ nhớ vật lý và thậm chí có thể được lưu trữ cả trong đĩa cứng.
So với các hệ thống không dùng kĩ thuật bộ nhớ ảo, các hệ thống dùng kĩ thuật này cho phép việc lập trình các ứng dụng lớn được dễ dàng hơn và sử dụng bộ nhớ vật lý thực (ví dụ RAM) hiệu quả hơn.
Lưu ý rằng:
- khái niệm "bộ nhớ ảo" không chỉ có nghĩa "sử dụng không gian đĩa để mở rộng kích thước bộ nhớ vật lý" - nghĩa là chỉ mở rộng hệ thống bộ nhớ để bao gồm cả đĩa cứng.
- Việc mở rộng bộ nhớ tới các ổ đĩa chỉ là một hệ quả thông thường của việc sử dụng các kĩ thuật bộ nhớ ảo.
- Định nghĩa của "bộ nhớ ảo" có nền tảng là việc định nghĩa lại không gian địa chỉ bằng một dải liên tục các địa chỉ bộ nhớ ảo để "đánh lừa" các chương trình rằng chúng đang dùng các khối lớn các địa chỉ liên tục.
3. Virtual Address - Địa chỉ ảo:
- Trong cái vùng bộ nhớ ảo ở trên đã nói, để cho tiến trình (process) sử dụng, hệ điều hành dễ hiểu, hai bên này cùng nhau quy định chia nhỏ ra theo từng byte, và đánh số từ 1 đến hết- Ô nhớ đã được đánh số là i thì ta nói địa chỉ của cái ô nhớ đó là i
Example: int a;
=> Trong tiến trình hiểu là: a nằm trong cái ô thứ 0x006e6e1 thì a có địa chỉ là 0x006e6e1
=> Trong hệ điều hành hiểu là: cái địa chỉ này tương đương với ô nào đó trong thanh RAM mà ta quản lý
= => Trong window 32 bit (xp, vista, 7) địa chỉ ảo có độ dài là 32 bits
- => Trong window 32 bits thì không gian địa chỉ ảo có địa chỉ từ 0000000h -> 7FFFFFFF
4. Con trỏ là gì?
- Miền giá trị của biến con trỏ là địa chỉ ô nhớ- Là môt biến bình thường
- Chứa cái 'địa chỉ ảo'.
5. Pointer declaration and initialization pointer in C and Pascal
Pointer declaration (C/C++)
Type *pointer
void *p
char *p
double *p
(we said p is pointer, not said *p is pointer)
Pointer declaration (Pascal)
var variables:^pointer
var i:^integer;
var p:pointer
var p:^char
var p:^double
(we said p is pointer, not said ^p is pointer)
Initialization pointer pointer (C/C++)
pointer = address
int a = 7777;
int *p = &a;
int *p;
P = &a;
Initialization pointer pointer (Pascal)
variables:@address
var x:integer
var p:pointer
p = @x;
Notes:
a = 3 & 2; // toán tử & hai ngôi
p = &a; // toán tử & một ngôi
scanf (“%d”, &a); // toán tử & một ngôi, là toán tử lấy địa chỉ một biến
when you declare; int *p = &a (or p = &a) <===> *p Equal with a
a = 2; <=> *p = 2;
a++; <=> (*p)++; // (*p) trong ngoặc vì toán tử * có độ ưu tiên thấp hơn ++
b = c + a – 200; <=> b = c + (*p) – 200;
(*p) = (*p) – 777; <=> a = a – 777;
scanf(“%d”, &a); <=> scanf(“%d”, p);
6. Pointer arithmetic - các phép toán trên con trỏ
Assignments - Phép gán:
- Tất cả các loại con trò đều có phép gán- Phép gán với con trỏ yêu cầu vế trái là 1 con trỏ và vế phải là 1 địa chỉ
- Phép gán yêu cầu sự tương xứng về kiểu dữ liệu, nếu ko tương xứng chúng ta phải ép kiểu
Example: p=(int*)7777;
- p có kiểu dữ liệu là int*
- còn 7777 là 1 hằng số nguyên, nên phải ép kiểu về int* rồi thực hiện phép gán
Compare - Phép so sánh:
- Phép so sánh bằng dùng để:o Kiểm tra hai con trỏ có trỏ vào cùng một vùng nhớ hay không?
o Kiểm tra một con trỏ có đang trỏ vào NULL hay không? (trong trường hợp cấp phát động, mở file, resource, …)
- Phép so sánh lớn, nhỏ hơn <, >, <=, >= dùng để:
o Kiểm tra độ thấp cao giữa hai địa chỉ, con trỏ nào nhỏ hơn thì trỏ vào địa chỉ thấp hơn
o Được so sánh mọi con trỏ với 0, vì 0 chính là NULL
Example 1st:
// huuvi168@gmail.com
// Last modified: 2016-06-28
int a=778,*p=&a;
double *y;
p==&a;
main==0;
p==0;
y==0;
Example 2nd:
int a=197,*p=&a;
double b=0,*x=&b;
// so sánh 2 con trỏ
int)p==(int)x;
p==(int *)x;
(double*)p==x;
(void*)p==(void*)x;
p==(void*)x;
(float*)p==(float*)x;
//so sánh con trỏ với số nguyên
p==(int*)9999;
int(p)==9999;
// con trỏ void có thể so sánh với con tất cả con trỏ khác
(int(*)())p==main;
Incrementing a Pointer, Decrementing a Pointer - Phép cộng trừ, tăng giảm: + += - -= -- ++ +
- Tăng/ giảm con trỏ p đi 1 đơn vị là cho p trỏ đến ô nhớ bên cạnh phía dưới/trên.
Chú ý:
+ Khi tăng giảm con trỏ p đo 1 đơn vị không có nghĩa là trỏ sang byte bên cạnh
+ Việc tăng giảm con trỏ đi 1 đơn vị phụ thuộc vào kiểu dữ liệu và nó trỏ đến, quy tắc là
p+1 => giá trị chứa trong p + sizeof (kiểu dữ liệu của biến mà p trỏ đến)
Tuy nhiên:
+ Không có phép tăng giảm trên con trỏ void
+ Không có phép tăng giảm trên con trỏ hàm
+ Không có phép cộng 2 con trỏ với nhau
+ Phép trừ 2 con trỏ trả về độ lệch pha giữa 2 con trỏ
Example:
void main()
{
char xau[200];
printf("Nhap xau : ");
scanf("%[a-zA-Z ]",xau);
//Viết hoa xâu (duyệt xuôi)
printf("Viet hoa : ");
//viết đầy đủ sẽ là (char *p=xau;*p!=NULL;p++)
for (char *p=xau;*p;p++) //p trỏ đến xâu; kí tự trỏ đến khác NULL;p=p+1
printf("%c",toupper(*p));
//Viết đảo ngược xâu (duyệt ngược)
printf("\nDao nguoc xau : ");
// cho p trỏ vào từ cuối cùng; p còn lớn hơn xau;p=p-1
for(char *p=xau+strlen(xau)-1;p>=xau;p--)
printf("%c",*p);
getch();
}
7. Pointer Constant - Hằng con trỏ? Con trỏ hằng
Hằng Con Trỏ | Con Trỏ Hằng |
---|---|
Những con trỏ mà chỉ trỏ cố định vào 1 vùng nhớ , những con trỏ này ko có khả năng trỏ vào vùng nhớ khác, ko thay đổi được (1) -> gọi là Hằng con trỏ | Những con trỏ mà trỏ vào một vùng nhớ cố định, con trỏ này chỉ có tác dụng trỏ đến, chứ không có khả năng thay đổi giá trị của vùng nhớ này, con trỏ này được ứng dụng gần như là tác dụng của phương thức hằng trong OOP (2) -> gọi là Con trỏ hằng |
Example: Hằng con trỏ
void main()
{
char buf[] = "bonjour";
char * const p = buf;
p++; // báo lỗi tại đây
p[4]++; // hoàn toàn có thể thay đổi giá trị vùng nhớ mà p trỏ đến
}
Example: Con trỏ Hằng:
void main()
{
char *p="learn tech tips";
p++;
(*p)++; // không báo lỗi biên dịch nhưng lỗi run-time
p[2]='b'; // không báo lỗi biên dịch nhưng lỗi run-time)
}
char buf[] = "bonjour";
char const *p = buf; // const char *p = buf;
p++; /* đúng */
p[4]++; /* ko được, sai */
Mục đích và ứng dụng Con trỏ Hằng
Ứng dụng lớn nhất của char const * đó là chú ý khi khai báo và sử dụng các hàm trả về constconst char *FunctionofAnotherPeople(void)
{
return "abc";
}
void HamCuaToi(void)
{
//gọi và sử dụng đến kết quả hàm bên trên thế nào?
char const *pstr=FunctionofAnotherPeople(); // how to used?
}
Khi code trong 1 project lớn. Giả sử bạn có 1 hàm, thao tác với 1 mảng, hàm này chỉ đọc mảng thôi, ko làm thay đổi các giá trị trong mảng . Và quan trọng là, khi share code cho các bạn khác trong cùng project, làm sao để họ biết điều này?
Vậy ta sẽ cài đặt hàm của mình như sau
// đối với trường hợp hằng con trỏ là tham số hình thức thì
// void ham(const int *) và void ham(int const *) => là như nhau, từ const khi đóng góp vào trong tham số hình thức là như nhau.
void input(const int *a,int n)
{
//xử lý gì đó
}
void main()
{
int a[100]={1, 77},
int n=2;
input(a,n); // khi sử dụng hàm này có nghĩa là hàm này không thay đổi mảng a của mình đâu, yên tâm sử dụng, nếu có lỗi gì đó thì ko phải sinh ra từ đây...
}
Phụ lục, Con trỏ Hằng (nâng cao)
int a=3;
const int *p;
p=&a; // Bản thân p thì có thể thay đổi, cho p gán vào chỗ khác được tuy nhiên
(*p)++; // Báo lỗi
8. Array - Pointer
Khi ta khai báo mảng thì tương đương với:
Xin cấp phát 1 vùng nhớ có kich thước như khai báo và khai báo ra một Hằng con trỏ trỏ vào đầu vùng nhớ đó?
Bài toán ở trên:
int a[100];
- Có thể coi a là một hằng con trỏ trỏ vào phần tử thứ 0 của mảng, a mang đầy đủ tính chất của một hằng con trỏ nhưng có thêm 1 số khác biệt nhỏ (ví dụ khi dùng size of)
- Các phép toán nhằm làm a trỏ tới vùng khác (thay đổi giá trị của a) là ko thể (++ -- = )
- a tương đương với &a[0]
- a+i tương đương với &a[i]
- *a tương đương với a[0]
- *(a+i) tương đương với a[i]
- 3[a] tương đương với *(a+3) tương đương với a[3]
Chú ý : trình biên dịch luôn hiểu a[i] là *(a+i)
- Các phần tử được sắp xếp liên tiếp trên bộ nhớ => đặc điểm cơ bản của mảng 1 chiều
- Sử dụng toán tử sizeof đối với mảng trên để trả về kích thước của mảng
- Chỉ cần 1 con trỏ trỏ vào đầu mảng là ta có thể kiểm soát được cả mảng rồi bằng cách lấy tên mảng + i là độ lệch
- Việc sử dụng a[i] để kiểm soát cũng chính là +i
Source code 1
// huuvi168@gmail.com
void main()
{
float a[100];
int n;
//nhập n
printf("Input n :");
scanf("%d",&n);
// nhập mảng
for(int i=0;i<n;i++)
{
printf("Input the elem: %d",i+1);
scanf("%f",a+i);
}
// xuất mảng
printf("Array output : \n");
for(int i=0;i<n;i++)
printf("%f ",*(a+i));
getch();
}
Source code 2
#include <stdio.h>
#include <conio.h>
void main()
{
int a[100]={0,1,2,3,4,5,6};
printf("%d",2[a]); //in ra 2, tại sao vậy ?
getch();
}
2[a] là gì?
Thật ra :
2[a] trình biên dịch sẽ hiểu là *(2+a)
*(2+a) hoàn toàn tương đương với *(a+2)
mà *(a+2) chính là a[2]
vậy 2[a] cũng đơn giản là a[2]
9. String - Xâu Ký tự
- Xâu kí tự là trường hợp đặc biệt của mảng 1 chiều khi mà cách thành phần của mảng là 1byte- Xâu kí tự kết thúc bằng NULL. NULL là 1 kí tự đặc biệt có mã là 0,
Có 3 cách viết NULL trong C như sau : NULL , '\0' , 0
Các trường hợp bị SAI
Chưa có bộ nhớ đã sử dụng
char *xau;
gets(xau);
Code trên vẫn biên dịch được, nhưng khi chạy sẽ sinh ra lỗi run-time, ở 1 số trình biên dịch ngày xưa thì có thể không bị lỗi đâu
Nhưng sai thì vẫn là sai, code này sai thuộc loại chưa cấp phát
Thay đổi giá trị của một hằng
char *xau="huuvi168@gmail.com";
xau[6]='A';
Explain
vẫn biên dịch được, nhưng khi chạy sẽ sinh ra lỗi run-time, lỗi này là lỗi cố tình thay đổi giá trị của 1 hằng
Nguyên nhân sâu xa của vấn đề như sau :
Khi khai báo char *xau="huuvi168@gmail.com"; thì bản chất là
+ Trong vùng nhớ data của chương trình sẽ có 1 hằng chuỗi "huuvi168@gmail.com" . (chuỗi chuỗi, đã là hằng thì ko thể bị thay đổi)
+ Cho con trỏ xau trỏ đến đầu của vùng nhớ đó.
Câu lệnh tiếp theo xau[6]='A'; cố tình thay đổi giá trị của hằng, rõ ràng là sinh ra lỗi rồi
Cố tình thay đổi giá trị của hằng con trỏ
char xau[100];
xau="Zidane ViLH"; // không biên dịch được
char *xau="huuvi168@gmail.com";
xau[6]='A';
Explain
Vì phép toán trên có nghĩa là khai báo 1 chuỗi "Zidane ViLH" trong vùng nhớ code, rồi sau đó cho hằng con trỏ xâu trỏ vào đó, Rất tiếc xau là hằng con trỏ nên ko thể trỏ đi đâu khác được, ngoài vị trí đã được khởi tạo trong câu lệnh khai báo
Notes
char xau[100]="Zidane ViLH"; hoặc char xau[100]={0}; thì hoàn toàn hợp lệ
Dùng phép toán so sánh để so sánh nội dung 2 xâu
void main()
{
char xau[100]="learntechtips";
if (xau == "learntechtips")
{
...
}
}
Explain
if (xau == "learntechtips")
=> chỗ này không sai về syntax, ko sinh ra lỗi runtime, nhưng mang lại kết quả ko như người dùng mong muốn, vì như phép toán của con trỏ, ta có Phép so sánh ngang bằng dùng để, kiểm tra 2 con trỏ có trỏ vào cùng 1 vùng nhớ hay không, hoặc kiểm tra 1 con trỏ có phải là đang trỏ vào NULL hay không? chứ ko phải là phép so sánh nội dung của xâu ký tự
Do đó để so sáng nội dung của xâu ta phải dùng những hàm strcmp (string compare) hoặc stricmp hay những hàm tự định nghĩa
Mỗi chương trình thực hiện 1 hàm, 1 khối lệnh thì các đối tượng cục bộ khai báo trong khối hay hàm đó được tổ chức lưu trữ trong ngăn xếp (stack).
Stack phình to ra, nở rộng xuống phía dưới,
khi hàm kết thức, các đối tượng cục bộ được giải phóng, stack nhỏ lại
10. Dynamically allocated? - Cấp phát động?
Cú pháp C:
- Dùng malloc, freecontro = (Ép kiểu) malloc ( … )
- malloc trả về 1 địa chỉ đến 1 vùng nhớ và coi vùng nhớ này là void *, nên trong câu lệnh malloc luôn đi kèm với việc ép kiểu
- Cấp phát là luôn phải đi kèm với giải phóng, ở đâu cũng thế, malloc là phải free, ok ? Code mà để thoát chương trình rồi chưa giải phóng cho dù là có hệ thống có tự giải phóng đi nữa vẫn bị coi là CODE KHÔNG TỐT !!!!
- Trong java chỉ cần cho reference = null là nó giải phóng nhưng trong C thì bắt buộc phải có thao tác giải phóng free()
Cú pháp C++:
- Dùng new, delete
int *p = new int();
delete (p);
Cú pháp pascal:
- Dùng new, dispose
var intPointer: ^integer;
new (intPointer);
dispose(intPoinster);
Sự khác nhau giữ malloc và new?
- Malloc là hàm, cấp phát trả về void*,- Malloc ko gọi hàm tạo, free không gọi hàm hủy
- Malloc trả về NULL nếu thất bại
- New là toán tử, new gọi hàm tạo, new có thể được đa năng hóa (nạp chồng)
- New ném ra exception nếu thất bại
- Toán tử new new và toán tử new[] không có khả năng realloc
- Dùng để cấp phát bộ nhớ to nếu dùng a[1000000] -> không cho cấp phát vì vùng Stack của mỗi chương trình là CÓ giới hạn, vì vậy phải dùng kỹ thuật cấp phát động xem lại Bộ nhớ ảo và Địa chỉ Ảo
11. Con trỏ Hàm
- Bản chất của con trỏ hàm cũng là 1 con trỏ có định kiểu
- Sử dụng con trỏ hàm để gọi hàm (invoke) khi biết địa chỉ
Source code - Gọi nội hàm
// http://learn-tech-tips.blogspot.com
#include <stdio.h>
#include <conio.h>
int min(int a,int b)
{
if (a>b) return a;
return b;
}
void main()
{
int (*p)(int,int);
p=min;
printf("min cua 4 va 5 la %d", p(4,5));
getch();
}
Chú ý:
khi khai báo ta phải dùng toán tử () với ý nghĩa là * này thuộc về p, là 1 con trỏ hàm. int (*p)(int,int);
Source code - Gọi ngoài hàm
void (*p) (int)
p = (void (*)(int)) 0x873AB;
p(3); // gọi hàm với tham số là 3
12. Hằng con trỏ hàm
Khi khai báo một hàm, tên của hàm chính là hằng con trỏ hàm (con trỏ này cố định vao vùng nhớ của hàm). => hằng con trỏ hàm cũng gần gần giống như khái niệm hằng con trỏ với mảng một chiều(int) p = int (main)
P == (int*)main;
(int (*)()) p == main;
P = (void*)main;
Ứng dụng của con trỏ hàm:
- Gọi hàm từ chương trình khác, làm auto game võ lâm bấm Ctrl + Z mở hộp thư
- Nguyên tắc là: dùng kỹ thuật hook để cái 1 Thread vào trong game. Thread này khi người dùng ấn nút Ctrl+Z nó sẽ gọi hàm mở hộp thư ingame
Example:
Giả sử ta có hàm dạng: open(int a) tại địa chỉ 0x007712CE thì để gọi hàm này minh làm:
void (*p) (int);
p = (void (*) (int)) 0x00712CE;
p(7); // gọi hàm với tham số là 7
Summary
- Miền giá trị của biến con trỏ là địa chỉ ô nhớ1. p point to a (p trỏ đến a)
Source code:
int a = 7777;
int *p;
P trỏ đến a khi giá trị trong p chứa địa chỉ ảo của a
Error
int a = 7777;
int *p;
p = a (or *p = a) -> Không có dấu &
Như hình trên p trỏ vào 1 ô nhớ mà không hề biết tới. không hề biết rằng ô nhớ này có bộ nhớ vật lý hay chưa, cũng không hề biết rằng nó chứa cái gì trong đấy/ nằm ở vùng nhớ nào
=> Do đó khả năng gây lỗi run-time là vô cùng lớn
2. Nhiều con trỏ cùng trỏ vào một ô nhớ
Source code:
int a = 7777;
int *p, *q;
p = &a;
q = &a; // or q = p;
- Tuy Hai biến cùng trỏ đến một địa chỉ ảo thì nó vẫn nằm trong ba biến CON TRỎ khác nhau
- Do đó khi thay đổi giá trị a từ 7777 sang 8888 thì giá trị của con trỏ p và con trỏ q cũng khác nhau (nhưng địa chỉ của hai con trỏ p, q trỏ tới vẫn giống nhau là 0x00778EA1)
3. Ép kiểu con trỏ
Source code
int *p = (int *)0x0077EFA1
char *p = (char*)0x0088ADD3
Nhưng, con trỏ void thì thoải mái, ép kiểu gì cũng được
void * p = (double *)0x00778BDE
void * p = (char *)0x00778BDE
void * p = (int *)0x00778BDE
4. Tăng giảm con trỏ:
Source code:
double a = 7777;
double *p;
p = &a
p++ // p = p + 1;
Do p trỏ đến một biến double nên việc +1 sẽ làm tương ứng với giá trì trong p + 8 đơn vị
Vì sizeof(double) = 8
p = p + 1 <=> 0x00778EA0 + 0x8 = 0x00778EA8
p = p – 1 <=> 0x00778EA0 – 0x8 = 0x00778E98
[Sưu Tầm Internet + sách kỹ thuật lập trình C++/Pascal + Góp ý]
0 comments:
Post a Comment