29.7 Using the * Indirection operatorLet's look at the third use of the * symbol in C#. You have seen * used:
When applied on a single operand in unsafe codes, the * symbol becomes the indirection [14] operator.
The indirection operator is used to obtain the value of the variable to which the pointer points. It can only be applied to a pointer type with the exception of void* . Applying it to a non-pointer type (such as a managed type) or to the void* pointer type results in a compilation error. For example, if pInt is a pointer, *pInt will mean 'the value stored at the address stored in the pointer pInt '. Let's continue our example where we left off: 10: int myInt = 3; 11: int* pInt = &myInt; 12: int yourInt = *pInt; In the third statement (line 12), a new int variable is declared and called yourInt , and has assigned to it the value stored at the address location stored in pInt . Now, pInt is storing the address 990A 16 . Going over to that address retrieves the value stored there (which is 3 ). Since pInt is of referent type int , it is supposed to store the address of an int , and that tells the compiler to retrieve only four bytes from 0x990A . The statement on line 12 results in the memory map shown in Figure 29.5. Figure 29.5. The final memory map.
The expression *pInt means:
Having gone through both the indirection pointer and address-of operator, it's time to see a full example. Study the following program. 1: using System; 2: 3: public class TestClass{ 4: 5: public unsafe static void Main(){ 6: double d1 = 3.45; 7: double* pD = &d1; 8: double d2 = *pD; 9: 10: Console.WriteLine("d1 :"+ d1); <-- 1 11: Console.WriteLine("&d1 :"+ (int)&d1); <-- 2 12: 13: Console.WriteLine("pD :"+(int)pD); <-- 3 14: Console.WriteLine("&pD :"+(int)&pD); <-- 4 15: 16: Console.WriteLine("d2 :"+ d2); <-- 5 17: Console.WriteLine("&d2 :"+ (int)&d2); <-- 6 18: } 19: }
If you are using csc.exe , you must compile any source file containing unsafe codes with the /unsafe flag: c:\expt>csc /unsafe test.cs Output: [15]
C:\expt>test d1 :3.45 &d1 :1243312 pD :1243312 &pD :1243320 d2 :3.45 &d2 :1243328 Here is another example. This time, a pointer is declared to a user -defined struct called Temp . 1: using System; 2: 3: public class TestClass{ 4: 5: public struct Temp{ <-- 1 6: public int a; 7: public int b; 8: } 9: 10: public unsafe static void Main(){ 11: Temp t1 = new Temp(); 12: t1.a = 1; 13: t1.b = 1; 14: 15: Console.WriteLine("t1.a : " + t1.a); // 1 16: 17: Temp* pTemp = &t1; 18: Temp t2 = *pTemp; 19: 20: t2.a = 9; 21: Console.WriteLine("t1.a : " + t1.a); // 1 22: Console.WriteLine("t2.a : " + t2.a); // 9 23: 24: int* pA = &t1.a; 25: int myInt = *pA; 26: Console.WriteLine("myInt: " + myInt); // 1 27: } 28: }
Output: c:\expt>test t1.a : 1 t1.a : 1 t2.a : 9 myInt: 1 Note that the statement on line 18 makes a copy of t1 , so that t1 and t2 are stored at unique addresses. Changing t2.a (line 20) will not affect t1.a . On line 24, a new int pointer is created which stores the address of t1 's int field a . This is perfectly legal. Remember that a struct containing a managed type is no longer considered an unmanaged type itself. In the code fragment below, NonPointerStruct is no longer considered to be unmanaged because it contains a AnotherStruct field. AnotherStruct contains a reference to object , a managed type. An unmanaged struct may not contain any reference to any managed type at any level of nesting. 10: public struct NonPointerStruct { <-- 1 11: int a; 12: int b; 13: AnotherStruct another; 14: } 15: 16: public struct AnotherStruct { <-- 1 17: object o; <-- 2 18: } 19: 20: public unsafe static void Main(){ 21: NonPointerStruct* pNps; 22: }
Compilation error: c:\expt>csc /unsafe test.cs test.cs(21,20): error CS0208: Cannot take the address or size of a variable of a managed type ('TestClass.NonPointerStruct') |