1. Trang chủ
  2. » Công Nghệ Thông Tin

Các Chủ Đề Tiến Bộ Trong C# part 8

16 368 1
Tài liệu đã được kiểm tra trùng lặp

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

THÔNG TIN TÀI LIỆU

Thông tin cơ bản

Tiêu đề Các mã không an toàn
Thể loại Bài giảng
Định dạng
Số trang 16
Dung lượng 179,92 KB

Các công cụ chuyển đổi và chỉnh sửa cho tài liệu này

Nội dung

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í

Trang 1

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

Trang 2

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;

Trang 3

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*

Trang 4

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;

Trang 5

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

Trang 6

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;

Trang 7

double d = 10.0;

uint *pUint= &u; // size of a uint is 4

byte *pByte = &b; // size of a byte is 1

double *pDouble = &d; // size of a double is 8

Giả sử địa chỉ của những con trỏ này trỏ đến là :

• pUint: 1243332

• pByte: 1243328

• pDouble: 1243320

sau khi thi hành ta có :

++pUint; // adds 1= 4 bytes to pUint

pByte -= 3; // subtracts 3=3bytes from pByte

double *pDouble2 = pDouble - 4; // pDouble2 = pDouble - 32 bytes (4*8 bytes)

Con trỏ sẽ có giá trị:

• pUint: 1243336

• pByte: 1243321

• pDouble2: 1243328

Ta cũng có thể trừ 2 con trỏ với nhau giá trị kết quả là kiểu long bằng giá trị con trỏ chia cho kích thước của kiểu mà nó đại diện Ví dụ :

double *pD1 = (double*)1243324; // note that it is perfectly valid to

// initialize a pointer like this

double *pD2 = (double*)1243300;

long L = pD1-pD2; // gives the result 3 (=24/sizeof(double))

Con trỏ đến Struct - Toán tử truy xuất các thành viên con trỏ

Cũng giống như con trỏ trong các kiểu dữ liệu có sẵn tuy nhiên thêm 1 điều kiện là - Struct không chứa bất kì kiểu tham chiếu nào.Do con trỏ không thể trỏ đến bất kì kiểu tham chiếu nào để tránh điều này , trình biên dịch sẽ phất cờ lỗi nếu ta tạo ra một con trỏ đến bất kì Struct nào chứa kiểu tham chiếu

Giả sử ta có struct như sau :

struct MyGroovyStruct

{

public long X;

public float F;

}

Trang 8

Sau đó ta định nghĩa con trỏ cho nó :

MyGroovyStruct *pStruct;

Khởi tạo nó :

MyGroovyStruct Struct = new MyGroovyStruct();

pStruct = &Struct;

Cũng có thể truy xuất các giá trị thành viên của 1 struct bằng con trỏ :

(*pStruct).X = 4;

(*pStruct).F = 3.4f;

Tuy nhiên cú pháp này hơi phức tạp C# định nghĩa 1 toán tử khác cho phép ta truy xuất các thành viên của Struct bằng con trỏ đơn giản hơn , gọi là toán tử truy xuất thành viên con trỏ ,kí hiệu là ->

Cách dùng :

pStruct->X = 4;

pStruct->F = 3.4f;

Ta cũng có thể thiết đặt trực tiếp con trỏ của kiểu tương đương để trỏ đến các trường trong Struct

long *pL = &(Struct.X);

float *pF = &(Struct.F);

hay :

long *pL = &(pStruct->X);

float *pF = &(pStruct->F);

Con trỏ đến các thành viên của lớp

Ta đã nói rằng không thể tạo ra con trỏ đến lớp.vì việc tạo có thể làm cho bộ gom rác hoạt động không đúng

tuy nhiên ta có thể tạo các con trỏ đến các thành viên của lớp Ta sẽ viết lại struct của ví

dụ trước như là lớp :

class MyGroovyClass

{

public long X;

public float F;

}

Trang 9

sau đó ta có thể tạo 1 con trỏ đến các trường của nó ,X và F.tuy nhiên làm như vậy sẽ gây

ra lỗi :

MyGroovyClass myGroovyObject = new MyGroovyClass();

long *pL = &( myGroovyObject.X); // wrong

float *pF = &( myGroovyObject.F); // wrong

Do X và F nằm trong 1 lớp , mà được đặt trong heap.nghĩa là chúng vẫn gián tiếp chịu sự quản lý của bộ gom rác.cụ thể bộ gom rác có thể quyết định di chuyển MyGroovyClass đến 1 vị trí mới trong bộ nhớ để dọn dẹp heap.Nếu làm điều này thì bộ gom rác tất nhiên

sẽ cập nhật tất cả các tham chiếu đến đối tượng ,giả sử như biến myGrooveObject vẫn sẽ trỏ đến đúng vị trí.Tuy nhiên bộ gom rác không biết gì về con trỏ cả vì thế nếu di chuyển các đối tượng tham chiếu bởi myGrooveObject,pL và pF sẽ vẫn không thay đôỉ và kết cuộc là trỏ đến sai vị trí vùng nhớ

Để giải quyết vấn đề này ta dùng từ khóa fixed , mà cho bộ gom rác biết rằng có thể có con trỏ trỏ đến các thành viên của các thể hiện lớp,vì thế các thể hiện lớp này sẽ không được di chuyển.cú pháp như sau nếu ta chỉ muốn khai báo 1 con trỏ :

MyGroovyClass myGroovyObject = new MyGroovyClass();

// do whatever

fixed (long *pObject = &( myGroovyObject.X))

{

// do something

}

nếu ta muốn khai báo nhiều hơn 1 con trỏ ta có thể đặt nhiều câu lệnh fixed trước khối

mã giống nhau :

MyGroovyClass myGroovyObject = new MyGroovyClass();

fixed (long *pX = &( myGroovyObject.X))

fixed (float *pF = &( myGroovyObject.F))

{

// do something

}

Ta có thể lồng các khối fixed nếu ta muốn fix các con trỏ trong các thời điểm khác nhau MyGroovyClass myGroovyObject = new MyGroovyClass();

fixed (long *pX = &( myGroovyObject.X))

{

// do something with pX

fixed (float *pF = &( myGroovyObject.F))

Trang 10

{

// do something else with pF

}

}

Ta cũng có thể khởi tạo vài biến trong cùng 1 khối fixed :

MyGroovyClass myGroovyObject = new MyGroovyClass();

MyGroovyClass myGroovyObject2 = new MyGroovyClass();

fixed (long *pX = &( myGroovyObject.X), pX2 = &( myGroovyObject2.X))

{

// etc

Thêm các lớp và Struct đến ví dụ

Trong phần này ta sẽ minh họa việc tính toán trên con trỏ và các con trỏ đến struct và lớp .Ta dùng ví dụ 2, PointerPlayaround2:

struct CurrencyStruct

{

public long Dollars;

public byte Cents;

public override string ToString()

{

return "$" + Dollars + "." + Cents;

}

}

class CurrencyClass

{

public long Dollars;

public byte Cents;

public override string ToString()

{

return "$" + Dollars + "." + Cents;

}

}

Bây giờ ta có thể áp dụng con trỏ cho các struct và lớp của ta ta bắt đầu bằng việc trình bày kích thước của stuct , tạo ra 1 vài thể hiện của nó cùng với con trỏ.ta dùng những con trỏ này để khởi tạo 1 trong những struct Currency ,amount1 và trình bày các địa chỉ của các biến :

Trang 11

public static unsafe void Main()

{

Console.WriteLine(

"Size of Currency struct is " + sizeof(CurrencyStruct));

CurrencyStruct amount1, amount2;

CurrencyStruct *pAmount = &amount1;

long *pDollars = &(pAmount->Dollars);

byte *pCents = &(pAmount->Cents);

Console.WriteLine("Address of amount1 is 0x{0:X}", (uint)&amount1);

Console.WriteLine("Address of amount2 is 0x{0:X}", (uint)&amount2);

Console.WriteLine("Address of pAmt is 0x{0:X}", (uint)&pAmount);

Console.WriteLine("Address of pDollars is 0x{0:X}", (uint)&pDollars);

Console.WriteLine("Address of pCents is 0x{0:X}", (uint)&pCents);

pAmount->Dollars = 20;

*pCents = 50;

Console.WriteLine("amount1 contains " + amount1);

Ta biết rằng amount2 sẽ được lưu trữ ở 1 địa chỉ ngay sau amount1, sizeof (

CurrencyStru) trả về 16, vì vậy CurrencyStruct sẽ nằm ở địa chỉ là bội số của 4 byte.do

đó sau khi giảm con trỏ currency , nó sẽ trỏ đến amount2:

pAmount; // this should get it to point to amount2

Console.WriteLine("amount2 has address 0x{0:X} and contains {1}",

(uint)pAmount, *pAmount);

Ta trình bày nội dụng của amount2 nhưng chưa khởi tạo nó Dù trình biên dịch C# ngăn không cho chúng ta dùng các giá trị chưa được khởi tạo nhưng khi dùng con trỏ thì điều này không còn đúng nửa.trình biên dịch không cách nào biết nội của amount2 mà ta trình bày, chỉ có ta biết

kết tiếp ta sẽ tính toán trên con trỏ pCents,pCents hiện thời trỏ đến amount1.Cents , nhưng mục đích của ta là làm cho nó trỏ đến amount2.Cents Làm điều này ta cần giảm địa chỉ của nó.ta cần làm một vài ép kiểu :

// do some clever casting to get pCents to point to cents

// inside amount2

CurrencyStruct *pTempCurrency = (CurrencyStruct*)pCents;

pCents = (byte*) ( pTempCurrency );

Console.WriteLine("Address of pCents is now 0x{0:X}", (uint)&pCents);

Cuối cùng ta dùng vài từ khoá fixed để tạo ra một vài con trỏ mà trỏ đến các trường trong thể hiện lớp,và dùng những con trỏ này để thiết đặt giá trị của thể hiện này.Lưu ý rằng

Ngày đăng: 07/11/2013, 18:15

HÌNH ẢNH LIÊN QUAN

Bảng kích thước kiểu : - Các Chủ Đề Tiến Bộ Trong C# part 8
Bảng k ích thước kiểu : (Trang 4)

TỪ KHÓA LIÊN QUAN

w