Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống
1
/ 16 trang
THÔNG TIN TÀI LIỆU
Thông tin cơ bản
Định dạng
Số trang
16
Dung lượng
179,92 KB
Nội dung
Các mã khơng an tồn Có trường hợp ta cần truy xuất nhớ trực tiếp ta muốn truy xuất vào hàm bên ngồi ( khơng thuộc NET) mà đòi hỏi trỏ truyền vào tham số( ví dụ hàm API ).hoặc ta muốn truy nhập vào nội dung nhớ để sửa lỗi Trong phần ta xem xét cách C# đáp ứng điều Con trỏ ( trình bày vắng tắt ) Con trỏ đơn giản biến lưu địa thứ khác theo cách tham chiếu khác biệt cú pháp C# tham chiếu không cho phép ta truy xuất vào địa nhớ ưu điểm trỏ : • • • Cải thiện thực thi : cho ta biết ta làm,đảm bảo liệu truy xuất hay thao tác theo cách hiệu - lí mà C C++ cho phép dung trỏ ngơn ngữ Khả tích hợp với phần trước ( Backward compatibility ) - ta phải sử dụng lại hàm API cho mục đích ta.Mà hàm API viết C,ngôn ngữ dùng trỏ nhiều, nghĩa nhiều hàm lấy trỏ tham số.Hoặc DLL hãng cung cấp chứa hàm lấy trỏ làm tham số Trong nhiều trường hợp ta viết khai báo DLlImport theo cách tránh sử dụng trỏ , ví dụ dùng lớp System.IntPtr Ta cần tạo địa vùng nhớ có giá trị cho người dùng - ví dụ ta muốn phát triển ứng dụng mà cho phép người dùng tương tác trực tiếp đến nhớ, debugger Nhược điểm : • • • • Cú pháp để lấy hàm phức tạp Con trỏ khó sử dụng Nếu khơng cẩn thận ta viết lên biến khác ,làm tràn stack, thông tin, đụng độ C# từ chối thi hành đoạn mã khơng an tồn (đoạn mã có sử dụng trỏ) Ta đánh dấu đoạn mã có sử dụng trỏ cách dùng từ khố 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 trường class MyClass { unsafe int *pX; // declaration of a pointer field in a class } Hoặc 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 đánh dấu biến cục unsafe int MyMethod() { unsafe int *pX; // WRONG } Để biên dịch mã chứa khối unsafe ta dùng lệnh sau : csc /unsafe MySource.cs hay csc -unsafe MySource.cs Cú pháp trỏ int * pWidth, pHeight; double *pResult; Lưu ý khác với C++ ,kí tự * kết hợp với kiểu kết hợp với biến - nghĩa ta khai báo pWidth pHeight trỏ có * sau kiểu int, khác với C++ ta phải khai báo * cho hai biến hai trỏ Cách dùng * & giống C++ : & : lấy địa * : lấy nội dung địa Ép kiểu trỏ thành kiểu Int Vì trỏ số int lưu địa nên ta chuyển tường minh 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 uint.sau ta chuyển ngược lại thành biến trỏ pD lý để ta phải ép kiểu Console.WriteLine khơng có overload nhận thơng số trỏ ta phải ép 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 kiểu trỏ Ta chuyển đổi tường minh 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ữ trỏ , không muốn đặc tả kiểu cho trỏ ta khai báo co ntrỏ void: void *pointerToVoid; pointerToVoid = (void*)pointerToInt; // pointerToInt declared as int* mục đích ta cần gọi hàm API mà địi hỏi thơng số void* Tốn tử sizeof Lấy thơng số tên kiểu trả số byte kiểu ví dụ : int x = sizeof(double); x có giá trị Bảng kích thước kiểu : sizeof(sbyte) = 1; sizeof(short) = 2; sizeof(int) = 4; sizeof(long) = 8; sizeof(char) = 2; sizeof(double) = 8; sizeof(byte) = 1; sizeof(ushort) = 2; sizeof(uint) = 4; sizeof(ulong) = 8; sizeof(float) = 4; sizeof(bool) = 1; Ta dùng sizeof cho struct khơng dùng cho lớp Ví dụ PointerPlayaround Ví dụ sau trình bày cách thao tác trỏ trình bày kết quả, cho phép ta thấy xảy nhớ nơi biến 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 biến • • • int x short y double z Cùng với trỏ trỏ đến giá trị này.sau ta trình bày giá trị biến kích thước,địa nó.Ta dùng đặc tả {0:X} Console.WriteLine để địa nhớ trình bày theo định dạng số bát phân Cuối ta dùng trỏ pX thay đổi giá trị x thành 20,và thử ép kiểu biến x thành double để xem điều sẻ xảy Biên dịch mã ,ta có kết 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 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ộng hay trừ số nguyên trỏ.Ví dụ , giả sử ta có trỏ trỏ đến số nguyên,và ta thử cộng vào giá trị trình biên dịch biết tăng vùng nhớ lên byte ( kiểu int có kích thước byte).nếu kiểu double cộng tăng giá trị trỏ lên byte ta dùng tốn tử +, -, +=, -=, ++,và với biến bên phía phải tốn tử long hay ulong Ví dụ uint u = 3; byte b = 8; double d = 10.0; uint *pUint= &u; // size of a uint is byte *pByte = &b; // size of a byte is double *pDouble = &d; // size of a double is Giả sử địa trỏ trỏ đến : • • • pUint: 1243332 pByte: 1243328 pDouble: 1243320 sau thi hành ta có : ++pUint; // adds 1= bytes to pUint pByte -= 3; // subtracts 3=3bytes from pByte double *pDouble2 = pDouble - 4; // pDouble2 = pDouble - 32 bytes (4*8 bytes) Con trỏ có giá trị: • • • pUint: 1243336 pByte: 1243321 pDouble2: 1243328 Ta trừ trỏ với giá trị kết kiểu long giá trị trỏ chia cho kích thước kiểu mà đạ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 (=24/sizeof(double)) Con trỏ đến Struct - Toán tử truy xuất thành viên trỏ Cũng giống trỏ kiểu liệu có sẵn nhiên thêm điều kiện Struct khơng chứa kiểu tham chiếu nào.Do trỏ khơng thể trỏ đến kiểu tham chiếu để tránh điều , trình biên dịch phất cờ lỗi ta tạo trỏ đến Struct chứa kiểu tham chiếu Giả sử ta có struct sau : struct MyGroovyStruct { public long X; public float F; } Sau ta định nghĩa trỏ cho : MyGroovyStruct *pStruct; Khởi tạo : MyGroovyStruct Struct = new MyGroovyStruct(); pStruct = &Struct; Cũng truy xuất giá trị thành viên struct trỏ : (*pStruct).X = 4; (*pStruct).F = 3.4f; Tuy nhiên cú pháp phức tạp C# định nghĩa toán tử khác cho phép ta truy xuất thành viên Struct trỏ đơn giản , gọi toán tử truy xuất thành viên trỏ ,kí hiệu -> Cách dùng : pStruct->X = 4; pStruct->F = 3.4f; Ta thiết đặt trực tiếp trỏ kiểu tương đương để trỏ đến trường Struct long *pL = &(Struct.X); float *pF = &(Struct.F); hay : long *pL = &(pStruct->X); float *pF = &(pStruct->F); Con trỏ đến thành viên lớp Ta nói khơng thể tạo trỏ đến lớp.vì việc tạo làm cho gom rác hoạt động khơng nhiên ta tạo trỏ đến thành viên lớp Ta viết lại struct ví dụ trước lớp : class MyGroovyClass { public long X; public float F; } sau ta tạo trỏ đến trường ,X F.tuy nhiên làm gây lỗi : MyGroovyClass myGroovyObject = new MyGroovyClass(); long *pL = &( myGroovyObject.X); // wrong float *pF = &( myGroovyObject.F); // wrong Do X F nằm lớp , mà đặt heap.nghĩa chúng gián tiếp chịu quản lý gom rác.cụ thể gom rác định di chuyển MyGroovyClass đến vị trí nhớ để dọn dẹp heap.Nếu làm điều gom rác tất nhiên cập nhật tất tham chiếu đến đối tượng ,giả sử biến myGrooveObject trỏ đến vị trí.Tuy nhiên gom rác khơng biết trỏ di chuyển đối tượng tham chiếu myGrooveObject,pL pF không thay đôỉ kết trỏ đến sai vị trí vùng nhớ Để giải vấn đề ta dùng từ khóa fixed , mà cho gom rác biết có trỏ trỏ đến thành viên thể lớp,vì thể lớp khơng di chuyển.cú pháp sau ta muốn khai báo trỏ : MyGroovyClass myGroovyObject = new MyGroovyClass(); // whatever fixed (long *pObject = &( myGroovyObject.X)) { // something } ta muốn khai báo nhiều trỏ ta đặt nhiều câu lệnh fixed trước khối mã giống : MyGroovyClass myGroovyObject = new MyGroovyClass(); fixed (long *pX = &( myGroovyObject.X)) fixed (float *pF = &( myGroovyObject.F)) { // something } Ta lồng khối fixed ta muốn fix trỏ thời điểm khác MyGroovyClass myGroovyObject = new MyGroovyClass(); fixed (long *pX = &( myGroovyObject.X)) { // something with pX fixed (float *pF = &( myGroovyObject.F)) { // something else with pF } } Ta khởi tạo vài biến khối fixed : MyGroovyClass myGroovyObject = new MyGroovyClass(); MyGroovyClass myGroovyObject2 = new MyGroovyClass(); fixed (long *pX = &( myGroovyObject.X), pX2 = &( myGroovyObject2.X)) { // etc Thêm lớp Struct đến ví dụ Trong phần ta minh họa việc tính tốn trỏ trỏ đến struct 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 ta áp dụng trỏ cho struct lớp ta ta bắt đầu việc trình bày kích thước stuct , tạo vài thể với trỏ.ta dùng trỏ để khởi tạo struct Currency ,amount1 trình bày địa biến : 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 amount2 lưu trữ địa sau amount1, sizeof ( CurrencyStru) trả 16, CurrencyStruct nằm địa bội số byte.do sau giảm trỏ currency , 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 amount2 chưa khởi tạo Dù trình biên dịch C# ngăn không cho dùng giá trị chưa khởi tạo dùng trỏ điều khơng cịn nửa.trình biên dịch khơng cách biết nội amount2 mà ta trình bày, có ta biết kết tiếp ta tính toán trỏ pCents,pCents thời trỏ đến amount1.Cents , mục đích ta làm cho trỏ đến amount2.Cents Làm điều ta cần giảm địa nó.ta cần làm vài ép kiểu : // 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 ta dùng vài từ khoá fixed để tạo vài trỏ mà trỏ đến trường thể lớp,và dùng trỏ để thiết đặt giá trị thể này.Lưu ý điều lần ta thấy địa mục lưu trữ heap stack: Console.WriteLine("\nNow with classes"); // now try it out with classes CurrencyClass amount3 = new CurrencyClass(); fixed(long *pDollars2 = &(amount3.Dollars)) fixed(byte *pCents2 = &(amount3.Cents)) { Console.WriteLine( "amount3.Dollars has address 0x{0:X}", (uint)pDollars2); Console.WriteLine( "amount3.Cents has address 0x{0:X}", (uint) pCents2); *pDollars2 = -100; Console.WriteLine("amount3 contains " + amount3); } chạy chương trình ta có : csc /unsafe PointerPlayaround2.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 PointerPlayaround2 Size of Currency struct is 16 Address of amount1 is 0x12F8A8 Address of amount2 is 0x12F898 Address of pAmt is 0x12F894 Address of pDollars is 0x12F890 Address of pCents is 0x12F88C amount1 contains $20.50 amount2 has address 0x12F898 and contains $5340121818976080.102 Address of pCents is now 0x12F88C Now with classes amount3.Dollars has address 0xBA4960 amount3.Cents has address 0xBA4968 amount3 contains $-100.0 Dùng trỏ để tối ưu hoá thực thi Sau ta áp dụng hiểu biết trỏ minh họa ví dụ mà ta thấy rõ lợi ích việc dùng trỏ thực thi Tạo mảng có Stack Để tạo mảng ta cần từ khoá stackalloc lệnh stackalloc dẫn thời gian chạy NET để cấp phát số vùng nhớ stack ta gọi ,ta cần cung cấp cho thơng tin • • Kiểu biến mà ta muốn lưu trữ Ta cần lưu biến ví dụ , để cấp phát đủ vùng nhớ lưu trữ 10 số thập phân decimal , ta viết : decimal *pDecimals = stackalloc decimal [10]; lệnh đơn giản cấp phát vùng nhớ không khởi tạo giá trị Để lưu 20 số double ta viết : double *pDoubles = stackalloc double [20]; dòng mã đặc tả số biến lưu hằng, điều định giá số lượng vào lúc chạy ta viết tương đương với ví dụ sau : int size; size = 20; // or some other value calculated at run-time double *pDoubles = stackalloc double [size]; Kiểu mảng mà có khối nhớ lưu phần tử sau : Câu hỏi đặt làm ta sử dụng vùng nhớ mà ta vừa tạo.trở lại ví dụ ta vừa nói giá trị trả từ stackalloc trỏ đến bắt đầu vùng nhớ.do cho phép ta lấy vị trí vùng nhớ cấp phát.ví dụ để cấp phát số double thiết lập phần tử ( phàn tử mảng) giá trị 3.0 tacó thể viết : double *pDoubles = stackalloc double [20]; *pDoubles = 3.0; Ta thiết lập phần tử thứ mảng cách dùng cách tính tốn trỏ mà ta biết Ví dụ ta muốn đặt giá trị phần tử thứ hai ta làm sau : double *pDoubles = stackalloc double [20]; *pDoubles = 3.0; *(pDoubles+1) = 8.4; Nó chung ta lấy phần tử thứ X mảng với biểu thức *(pDoubles+X) Bên cạnh C# định nghĩa cú pháp thay Nếu p trỏ X kiểu số biểu thức p[X] tương đương với *(p+X) double *pDoubles = stackalloc double [20]; pDoubles[0] = 3.0; // pDoubles[0] is the same as *pDoubles pDoubles[1] = 8.4; // pDoubles[1] is the same as *(pDoubles+1) Mặc dù mảng ta truy xuất theo cách mảng bình thường, ta cần quan tâm đến cảnh báo sau đoạn mã sau gây biệt lệ: double [] myDoubleArray = new double [20]; myDoubleArray[50] = 3.0; Biệt lệ xuất ta cố truy xuất vào mảng dùng mục vượt mảng ( mục 50 , giá trị lớn cho phép 19).Tuy nhiên ,nếu ta khai báo mảng dùng stackalloc , điều khơng gây biệt lệ : double *pDoubles = stackalloc double [20]; pDoubles[50] = 3.0; Ví dụ QuickArray Ta thảo luận trỏ với stackalloc ví dụ QuickArray ví dụ hỏi người dùng phần tử họ muốn cấp phát cho mảng sau ta dùng stackalloc để cấp phát mảng với độ dài đó.các phần tử mảng gán giá trị bình phương mục kết trình bày bên : using System; namespace Wrox.ProCSharp.AdvancedCSharp { class MainEntryPoint { static unsafe void Main() { Console.Write("How big an array you want? \n> "); string userInput = Console.ReadLine(); uint size = uint.Parse(userInput); long *pArray = stackalloc long [(int)size]; for (int i=0 ; i