Các mã không an toàn
Có những trường hợp ta cần truy xuất bộ nhớ trực tiếp khi ta muốn truy xuất vào các hàm
bên ngoài ( không thuộc .NET) mà đòi hỏi con trỏ được truyền vào như tham số( ví dụ
như các hàm API ).hoặc là vì ta muốn truy nhập vào nội dung bộ nhớ để sửa lỗi....Trong
phần này ta sẽ xem xét cách C# đáp ứng những điều này như thế nào.
Con trỏ
( trình bày vắng tắt )
Con trỏ đơn giản là 1 biế
n lưu địa chỉ của một thứ khác theo cùng 1 cách như là 1 tham
chiếu. sự khác biệt là cú pháp C# trong tham chiếu không cho phép ta truy xuất vào địa
chỉ bộ nhớ.
3 ưu điểm của con trỏ :
•
Cải thiện sự thực thi : cho ta biết những gì ta đang làm,đảm bảo rằng dữ liệu được
truy xuất hay thao tác theo cách hiệu quả nhất - đó là lí do mà C và C++ cho phép
dung con trỏ trong ngôn ngữ của mình.
•
Khả năng tích hợp với các phần trước ( Backward compatibility ) - đôi khi ta phải
sử dụng lại các hàm API cho mục đích của ta.Mà các hàm API được viết bằng
C,ngôn ngữ dùng con trỏ rất nhiều, nghĩa là nhiều hàm lấy con trỏ như tham
số.Hoặc là các DLL do 1 hãng nào đó cung cấp chứa các hàm lấy con trỏ
làm tham số . Trong nhiều trường hợp ta có thể viết các khai báo DLlImport theo
cách tránh sử dụng con trỏ , ví dụ như dùng lớp System.IntPtr.
•
Ta có thể cần tạo ra các địa chỉ vùng nhớ có giá trị cho người dùng - ví dụ nếu ta
muốn phát triển 1 ứng dụng mà cho phép người dùng tương tác trực tiếp đến bộ
nhớ, như là 1 debugger.
Nhược điểm :
•
Cú pháp để lấy các hàm phức tạp hơn
•
Con trỏ khó sử dụng
•
Nếu không cẩn thận ta có thể viết lên các biến khác ,làm tràn stack, mất thông tin,
đụng độ ...
•
C# có thể từ chối thi hành những đoạn mã không an toàn này (đoạn mã có sử dụng
con trỏ)
Ta có thể đánh dấu đoạn mã có sử dụng con trỏ bằng cách dùng từ khoá unsafe
Ví dụ : dùng cho hàm
unsafe int GetSomeNumber()
{
// code that can use pointers
}
Dùng cho lớp hay struct
unsafe class MyClass
{
// any method in this class can now use pointers
}
Dùng cho 1 trường
class MyClass
{
unsafe int *pX; // declaration of a pointer field in a class
}
Hoặc một khối mã
void MyMethod()
{
// code that doesn't use pointers
unsafe
{
// unsafe code that uses pointers here
}
// more 'safe' code that doesn't use pointers
}
Tuy nhiên ta không thể đánh dấu 1 biến cục bộ là unsafe
int MyMethod()
{
unsafe int *pX; // WRONG
}
Để biên dịch các mã chứa khối unsafe ta dùng lệnh sau :
csc /unsafe MySource.cs
hay
csc -unsafe MySource.cs
Cú pháp con trỏ
int * pWidth, pHeight;
double *pResult;
Lưu ý khác với C++ ,kí tự * kết hợp với kiểu hơn là kết hợp với biến - nghĩa là khi ta
khai báo như ở trên thì pWidth và pHeight đều là con trỏ do có * sau kiểu int, khác với
C++ ta phải khai báo * cho cả hai biến trên thì cả hai mới là con trỏ.
Cách dùng * và & giống như trong C++ :
& : lấy địa chỉ
* : lấy nội dung của địa chỉ
Ép kiểu con trỏ thành kiểu Int
Vì con trỏ là 1 số int lưu địa chỉ nên ta có thể chuyển tường minh con tr
ỏ thành kiểu int
hay ngược lại.Ví dụ:
int x = 10;
int *pX, pY;
pX = &x;
pY = pX;
*pY = 20;
uint y = (uint)pX;
int *pD = (int*)y;
y là uint.sau đó ta chuyển ngược lại thành biến con trỏ pD
1 lý do để ta phải ép kiểu là Console.WriteLine không có overload nào nhận thông số là
con trỏ do đó ta phải ép nó sang kiểu số nguyên int
Console.WriteLine("Address is" + pX); // wrong - will give a
// compilation error
Console.WriteLine("Address is" + (uint) pX); // OK
Ép kiểu giữa những kiểu con trỏ
Ta cũng có thể chuyển đổi tường minh giữa các con trỏ trỏ đến1 kiểu khác ví dụ :
byte aByte = 8;
byte *pByte= &aByte;
double *pDouble = (double*)pByte;
void Pointers
Nếu ta muốn giữ 1 con trỏ , nhưng không muốn đặc tả kiểu cho con trỏ ta có thể khai báo
co ntrỏ là void:
void *pointerToVoid;
pointerToVoid = (void*)pointerToInt; // pointerToInt declared as int*
mục đích là khi ta cần gọi các hàm API mà đòi hỏi thông số void*.
Toán tử sizeof
Lấy thông số là tên của kiểu và trả về số byte của kiểu đó ví dụ :
int x = sizeof(double);
x có giá trị là 8
Bảng kích thước kiểu :
sizeof(sbyte) = 1; sizeof(byte) = 1;
sizeof(short) = 2; sizeof(ushort) = 2;
sizeof(int) = 4; sizeof(uint) = 4;
sizeof(long) = 8; sizeof(ulong) = 8;
sizeof(char) = 2; sizeof(float) = 4;
sizeof(double) = 8; sizeof(bool) = 1;
Ta cũng có thể dùng sizeof cho struct nhưng không dùng được cho lớp.
Ví dụ PointerPlayaround
Ví dụ sau trình bày cách thao tác trên con trỏ và trình bày kết quả, cho phép ta thấy
những gì xảy ra trong bộ nhớ và nơi biến được lưu trữ:
using System;
namespace Wrox.ProCSharp.AdvancedCSharp
{
class MainEntryPoint
{
static unsafe void Main()
{
int x=10;
short y = -1;
byte y2 = 4;
double z = 1.5;
int *pX = &x;
short *pY = &y;
double *pZ = &z;
Console.WriteLine(
"Address of x is 0x{0:X}, size is {1}, value is {2}",
(uint)&x, sizeof(int), x);
Console.WriteLine(
"Address of y is 0x{0:X}, size is {1}, value is {2}",
(uint)&y, sizeof(short), y);
Console.WriteLine(
"Address of y2 is 0x{0:X}, size is {1}, value is {2}",
(uint)&y2, sizeof(byte), y2);
Console.WriteLine(
"Address of z is 0x{0:X}, size is {1}, value is {2}",
(uint)&z, sizeof(double), z);
Console.WriteLine(
"Address of pX=&x is 0x{0:X}, size is {1}, value is 0x{2:X}",
(uint)&pX, sizeof(int*), (uint)pX);
Console.WriteLine(
"Address of pY=&y is 0x{0:X}, size is {1}, value is 0x{2:X}",
(uint)&pY, sizeof(short*), (uint)pY);
Console.WriteLine(
"Address of pZ=&z is 0x{0:X}, size is {1}, value is 0x{2:X}",
(uint)&pZ, sizeof(double*), (uint)pZ);
*pX = 20;
Console.WriteLine("After setting *pX, x = {0}", x);
Console.WriteLine("*pX = {0}", *pX);
pZ = (double*)pX;
Console.WriteLine("x treated as a double = {0}", *pZ);
Console.ReadLine();
}
}
}
Mã gồm 3 biến
•
int x
•
short y
•
double z
Cùng với các con trỏ trỏ đến các giá trị này.sau đó ta trình bày giá trị của các biến và kích
thước,địa chỉ của nó.Ta dùng đặc tả {0:X} trong Console.WriteLine để địa chỉ bộ nhớ
được trình bày theo định dạng số bát phân.
Cuối cùng ta dùng con trỏ pX thay đổi giá trị của x thành 20,và thử ép kiểu biến x thành 1
double để xem điều gì sẻ xảy ra
Biên dịch mã ,ta có kết quả sau :
csc PointerPlayaround.cs
Microsoft (R) Visual C# .NET Compiler version 7.00.9466
for Microsoft (R) .NET Framework version 1.0.3705
Copyright (C) Microsoft Corporation 2001. All rights reserved.
PointerPlayaround.cs(7,26): error CS0227: Unsafe code may only appear if
compiling with /unsafe
csc /unsafe PointerPlayaround.cs
Microsoft (R) Visual C# .NET Compiler version 7.00.9466
for Microsoft (R) .NET Framework version 1.0.3705
Copyright (C) Microsoft Corporation 2001. All rights reserved.
PointerPlayaround
Address of x is 0x12F8C4, size is 4, value is 10
Address of y is 0x12F8C0, size is 2, value is -1
Address of y2 is 0x12F8BC, size is 1, value is 4
Address of z is 0x12F8B4, size is 8, value is 1.5
Address of pX=&x is 0x12F8B0, size is 4, value is 0x12F8C4
Address of pY=&y is 0x12F8AC, size is 4, value is 0x12F8C0
Address of pZ=&z is 0x12F8A8, size is 4, value is 0x12F8B4
After setting *pX, x = 20
*pX = 20
x treated as a double = 2.63837073472194E-308
Pointer Arithmetic
Ta có thể cộng hay trừ số nguyên trên con trỏ.Ví dụ , giả sử ta có 1 con trỏ trỏ đến số
nguyên,và ta thử cộng 1 vào giá trị của nó .trình biên dịch sẽ biết và tăng vùng nhớ lên 4
byte ( do kiểu int có kích thước 4 byte).nếu là kiểu double thì khi cộng 1 sẽ tăng giá trị
của con trỏ lên 8 byte.
ta có thể dùng toán tử +, -, +=, -=, ++,và -- với biến bên phía phải của toán tử này là long
hay ulong
Ví dụ
uint u = 3;
byte b = 8;