15

 

Unsafe code

 

Today, the programming language C is the most widely used because of only one reason and that is the use of pointers. In this chapter, we will explain what pointers are all about and how they can be used in the world of C#.

 

a.cs

class zzz

{

public static void Main()

{

yyy a = new yyy();

a.abc();

}

}

class yyy

{

public void abc()

{

int *i;

}

}

 

Compiler Error

a.cs(13,6): error CS0214: Pointers may only be used in an unsafe context

 

All simple variables like int, byte, short store numbers. When we create a variable in C#, we are allowed to put a multiplication/asterisk sign '*' in front of the variable. These variables are called Pointers. We get an error as C# considers pointers to be unsafe and hence we need special permission to use pointers.

 

a.cs

class zzz

{

public static void Main()

{

yyy a = new yyy();

a.abc();

}

}

class yyy

{

unsafe public void abc()

{

int *i;

}

}

 

Compiler Error

a.cs(11,20): error CS0227: Unsafe code may only appear if compiling with /unsafe

 

As seen in the previous chapter, the unsafe option must be tagged while compiling the program.  Give the command as csc a.cs /unsafe and the error now disappears.

 

a.cs

class zzz

{

public static void Main()

{

yyy a = new yyy();

a.abc();

}

}

unsafe class yyy

{

public void abc()

{

int *i;

}

}

 

The C# documentation very clearly states that the modifier can be used along with the class keyword, By using the modifier unsafe, we are asking C# to let us use pointers as we are unable to write code without the use of pointers.

 

a.cs

using System;

class zzz

{

public static void Main()

{

yyy a = new yyy();

a.abc();

}

}

class yyy

{

unsafe public void abc()

{

Console.WriteLine(sizeof(byte *) + " " + sizeof(short *) + " " + sizeof(int *) + " " + sizeof(long *));

 

Console.WriteLine(sizeof(byte **) + " " + sizeof(short **) + " " + sizeof(int **) + " " + sizeof(long **));

}

}

Output

4 4 4 4

4 4 4 4

 

Some time back, we had used the sizeof keyword, to determine how much memory C# allocates for our variables. When we ask for the sizeof an pointer variable, we always get 4, irrespective of whether the data type is a short or int or whatever, including another pointer.

 

a.cs

using System;

class zzz {

public static void Main() {

yyy a = new yyy();

a.abc();

}

}

class yyy {

unsafe public void abc() {

int  *i;

int j=1;

i = &j;

Console.WriteLine((int)i);

*i = 10;

Console.WriteLine(j);

}

}

 

Output

1243472

10

 

 

 

We have created a variable j that has been initialized to one. We have also created a variable i, with a multiplication sign at the time of creation. By doing this C# allocated four memory locations to store i. What is most important is that a pointer variable is just like any another variable. It simply stores numbers. The single big difference is that a pointer variable value is interpreted as a computer memory location. If both i and j were being initialized to 1, then j is the number 1 as we know it, whereas i stand for computer memory location 1. Pointers can only be initialized to a computer memory location. Whenever we place an ampersand(&) in front of a variable, we are asking C# to tell us about the memory allocated for the variable. Every time C# creates a variable, it stores it somewhere in memory. We are at times interested in knowing where in memory the variable was allocated. A & in front of j will tell us the memory location where j starts in memory. As it is a computer memory location or address we can store it in i. C# allocated 4 memory location for j and the start of these 4 are being stored in a pointer.

 

We would like to display the value of the pointer. The WriteLine function does not have an overload to display a pointer and we have to cast it to an int. As we get 1243472 displayed, it could only mean that the variable j begins here and is spread over memory locations 1243473, 1243474 and 1243475.

 

We can only place the multiplication sign in front of a variable defined to be a pointer. If i is not a pointer, *i will return an error. C# here asks a simple question. What is the value of the variable i? . The answer is 1243472. C# will now go to memory location 1243472 to 1243475 and place the value 10 there. As the value of j is determined by what is present from memory locations 1243472 to 1243475, the value of j changes from 1 to 10.

 

a.cs   

using System;

class zzz

{

public static void Main()

{

yyy a = new yyy();

a.abc();

}

}

class yyy

{

unsafe public void abc()

{

int  *i;

int j=1, k = 1;

i = &j;

Console.WriteLine((int)i);

*i = 10;

Console.WriteLine(j);

i = &k;

Console.WriteLine((int)i);

*i = 100;

Console.WriteLine(k + " " + j);

}

}

 

Output

1243468

10

1243464

100 10

 

 

 

Whenever we create a pointer variable, int *i, we are not stating explicitly which int i will be pointing to. Thus it can point to one int today, another int tomorrow. This is what gives pointers their flexibility. It can point to any int it wants to in memory. We are first initializing i to point to j, then we are changing the value of j to 10 through the pointer. Then i points to k in memory, and then k's value is indirectly being changed to 100 through the pointer. The first WriteLine tells us that the variable j begins at memory location 1243468. From here the next 4 have been reserved for the variable j. Memory for k has been allocated at 1243464. Thus, physically, k starts first in memory from 1243464 to 67 and j from 1243468 onwards.

 

a.cs

using System;

class zzz

{

public static void Main()

{

yyy a = new yyy();

a.abc();

}

}

class yyy

{

unsafe public void abc()

{

int  *i;

int j=1;

i = &j;

Console.WriteLine((int)i + " " + (int) &i);

}

}

 

Output

1243464 1243468

 

Lets not forget that pointers are also variables and C# allocates memory for i. An & in front of any variable tells us where it starts in memory. Thus i starts from memory location 1243468 onwards.

 

a.cs

using System;

class zzz

{

public static void Main()

{

yyy a = new yyy();

a.abc();

}

}

class yyy

{

unsafe public void abc()

{

long  *i;

long j=1, k = 1;

i = &j;

Console.WriteLine((int)i);

i = &k;

Console.WriteLine((int)i);

}

}

 

Output

1243460

1243468

 

The sizeof a long is 8 bytes and the addresses printed differ by 8 bytes.

 

a.cs

using System;

class zzz

{

public static void Main()

{

yyy a = new yyy();

a.abc();

}

}

class yyy {

unsafe public void abc(){

byte  *i;

byte j=1, k = 1;

i = &j;

Console.WriteLine((int)i);

i = &k;

Console.WriteLine((int)i);

}

}

 

Output

1243468

1243472

The only change here is that j and k are not longs but bytes. Why are we learning all about pointers is a question you should ask yourself. The C# documentation says very clearly that a byte occupies one memory location. The sizeof byte will also return one. In the above output, however, the memory locations differ by 4. The reason being that on the Pentium Processor, if we want one memory location, we will receive a minimum of 4. That's why, the Pentium is said to be a 32-bit processor. It will give you memory in chunks of 4. From this knowledge, using a byte instead of a int does not conserve memory or speed up your program. The above explanation cannot be comprehended without the knowledge of pointers.

 

a.cs

using System;

class zzz

{

public static void Main()

{

yyy a = new yyy();

a.abc();

}

}

class yyy

{

unsafe public void abc()

{

byte *i;

i = 0;

Console.WriteLine((int)i);

}

}

 

Compiler Error

a.cs(15,5): error CS0029: Cannot implicitly convert type 'int' to 'byte*'

 

C# is a strongly typed language and we are not allowed to convert a zero which is an int to a byte *, the data type of i. The only way out is to use a cast.

a.cs

using System;

class zzz

{

public static void Main()

{

yyy a = new yyy();

a.abc();

}

}

class yyy

{

unsafe public void abc()

{

byte *i;char *j;int *k;long *l;

i = (byte *)0; j = (char *)0; k = (int *)0 ; l = (long *)0;

Console.WriteLine((int)i + " " + (int) j + " " + (int) k + " " + (int)l);

i++;j++;k++;l++;

Console.WriteLine((int)i + " " + (int) j + " " + (int) k + " " + (int)l);

i++;j++;k++;l++;

Console.WriteLine((int)i + " " + (int) j + " " + (int) k + " " + (int)l);

}

}

 

Output

0 0 0 0

1 2 4 8

2 4 8 16

 

A pointer to any data type is allocated four memory locations at the time of creation. This enables pointers to store values that range from 0 to 4 billion. The question that comes to your mind, is what is the difference between a pointer to an int from a pointer to a long. We have created four variables i, j, k, l. Each is a pointer to a different data type. We have also initialized each variable to 0 and displayed their values using the WriteLine function. We have then incremented each of them by 1. To our surprise the pointer to an int k, increases by 4 and not by 1. The char pointer increases by 2 and long by 8. To make sure that is not a isolated phenomena, we increment them again. Same answer once again. They do not increase by one but by the size of the data type they point to. The sizeof a long is 8 and thus a pointer to a long increases by 8 and not 1.

 

When we initialized l to zero, we were telling C# that a long begins at memory location 0. This long will occupy the next 8 memory locations, form 0 to 7. Thus when we write l++, we are telling C# to take us to the next long in memory, which has to begins now at 8. The first difference between pointers to different data types is that the amount the pointer value increases is dependent upon what it is pointing to.

 

a.cs

using System;

class zzz

{

public static void Main()

{

yyy a = new yyy();

a.abc();

}

}

class yyy

{

unsafe public void pqr(int x , int y , int z)

{

int *p1; int *p2; int *p3;

p1 = &x; p2 = &y  ; p3 = &z;

Console.WriteLine((int) p1  + " " + (int) p2  + " " + (int) p3);

*p1 = 20;

*p2 = 30;

}

unsafe public void abc()

{

int *i ; int  *j ; int *k;

int l=1,m=2,n=3;

i=&l; j=&m; k=&n;

Console.WriteLine((int) i + " " + (int) j + " " + (int) k);

pqr(l,m,n);

Console.WriteLine(l + " " +  m + " " +  n); }}

Output

1243460 1243456 1243452

1243424 1243436 1243432

1 2 3

 

We have created a variable i of type int in memory. It begins at memory location 1243460. Similarly m and n start at 56 and 52 respectively. We are then calling a function pqr and passing three parameters to it. These are being stored at memory locations 1243424, 36 and 32 respectively. When we write *p1, we are going to memory location 1243424 and writing 20 there. This will change the value of x from 1 to 20. As variable l begins at 1243460, its value remains unchanged. The area of memory where x, y and z start is called the stack. Stack memory is used to pass parameters to functions.

 

a.cs

using System;

class zzz

{

public static void Main()

{

yyy a = new yyy();

a.abc();

}

}

class yyy

{

unsafe public void pqr(int x , int y , int z)

{

int *p1; int *p2; int *p3;

p1 = &x; p2 = &y  ; p3 = &z;

Console.WriteLine((int) p1  + " " + (int) p2  + " " + (int) p3);

}

unsafe public void xyz(int x , int y , int z)

{

int *p1; int *p2; int *p3;

p1 = &x; p2 = &y  ; p3 = &z;

Console.WriteLine((int) p1  + " " + (int) p2  + " " + (int) p3);

}

unsafe public void abc()

{

int *i ; int  *j ; int *k;

int l=1,m=2,n=3;

i=&l; j=&m; k=&n;

Console.WriteLine((int) i + " " + (int) j + " " + (int) k);

pqr(l,m,n);

xyz(l,m,n);

}

}

 

Output

1243460 1243456 1243452

1243424 1243436 1243432

1243424 1243436 1243432        

 

Now you will begin to comprehend the importance of pointers. Its is an understanding of pointers that will give you a better insight into understanding the innards of a programming language. The last two WriteLines display the same answer as the stack memory gets reused for every function call. The stack is an area of memory that will store parameters and variables created in a function. Let us assume that the stack begins at memory location 100. The parameters are first pushed onto the stack and then all the local variables are created later, but below the parameters. Remember that the stack grows down in memory. When the function is over, the stack is moved back to 100, and the next function that gets called, reuses the same memory from 100. Thus anything created in a function has a lifetime of the open and close braces as the next function uses the same memory and the earlier values get overwritten.

 

a.cs

using System;

class zzz

{

public static void Main()

{

yyy a = new yyy();

a.abc();

}

}

class yyy

{

unsafe public void xyz()

{

int i=0,j=0,k=0;

int *p1; int *p2; int *p3;

p1 = &i; p2 = &j  ; p3 = &k;

Console.WriteLine((int) p1  + " " + (int) p2  + " " + (int) p3);

}

unsafe public void aaa()

{

int x=0,y=0,z=0;

int *p1; int *p2; int *p3;

p1 = &x; p2 = &y  ; p3 = &z;

Console.WriteLine((int) p1  + " " + (int) p2  + " " + (int) p3);

}

unsafe public void pqr()

{

int i=0,j=0,k=0;

int *p1; int *p2; int *p3;

p1 = &i; p2 = &j  ; p3 = &k;

Console.WriteLine((int) p1  + " " + (int) p2  + " " + (int) p3);

}

unsafe public void abc()

{

pqr();

xyz();

aaa();

}

}

 

Output

1243460 1243464 1243468

1243460 1243464 1243468

1243460 1243464 1243468

  

 

The above program proves a number of points. C# reuses the same memory for passing parameters and creating variables in functions. The variable names are not important as they get created on the stack, at the same place in memory.

 

a.cs

using System;

class zzz

{

public static void Main()

{

yyy a = new yyy();

a.abc();

}

}

class yyy

{

unsafe public void pqr(int x , int y , int z)

{

int *p1; int *p2; int *p3;

p1 = &x; p2 = &y  ; p3 = &z;

Console.WriteLine((int) p1  + " " + (int) p2  + " " + (int) p3);

p3++;p3++;p3++;p3++;p3++;p3++;

Console.WriteLine((int) p3);

*p3=20;

p3++;

Console.WriteLine((int) p3);

*p3=200;

}

unsafe public void abc()

{

int *i ; int  *j ; int *k;

int l=1,m=2,n=3;

i=&l; j=&m; k=&n;

Console.WriteLine((int) i + " " + (int) j + " " + (int) k);

pqr(l,m,n);

Console.WriteLine(l + " " +  m + " " +  n);

}

}

Output

1243460 1243456 1243452

1243424 1243436 1243432

1243452

1243456

1 200 20

 

Pointers are really unsafe. There is a simple rule in C# which says that variables created in one function abc, cannot be changed by another function pqr. The variables m and n begin at memory locations 1243456 1243452 respectively. If there was some way, I could write to these memory locations, I would be changing the values of m and n respectively. The pointer p3 stores, where the parameter z is stored in memory i.e. 1243432. If we can increment this pointer 6 times, 6 will actually be 24 due to pointer arithmetic, the value of p will now be 1243452 which is the address of n in memory. Thus from one function, I can change the value of another variable. In this case we are doing it on purpose but if I called a function, where a pointer went haywire, my variables change and nobody knows why the program stops working. Thus pointers are like a sharp knife, it can save a life in the hands of a doctor but in the wrong hands it can also kill.

 

a.cs

using System;

class zzz

{

public static void Main()

{

yyy a = new yyy();

a.abc();

}

}

class yyy

{

unsafe public void pqr(int x , int y , int z)

{

int *p1; int *p2; int *p3;

p1 = &x; p2 = &y  ; p3 = &z;

Console.WriteLine((int) p1  + " " + (int) p2  + " " + (int) p3);

int q = 1;

while ( q <= 12 )

{

p1++;

Console.WriteLine((int) p1 + " " + *p1);

q++;

}

}

unsafe public void abc()

{

int *i ; int  *j ; int *k;

int l=1,m=2,n=3;

i=&l; j=&m; k=&n;

Console.WriteLine((int) i + " " + (int) j + " " + (int) k);

pqr(l,m,n);

Console.WriteLine(l + " " +  m + " " +  n);

}

}

 

Output

1243460 1243456 1243452

1243424 1243436 1243432

1243428 47645012

1243432 3

1243436 2

1243440 0

1243444 1243508

1243448 1243544

1243452 3

1243456 2

1243460 1

1243464 1243452

1243468 1243460

1243472 1243484

1 2 3

 

Would it not be a great idea to be able to display whatever is there on the stack. Between variables there are some gaps and the above program is displaying the contents of the stack. The variable q makes the loop go on 12 times and each time we increment p1 by 4. P1 is pointing to x the first parameter on the stack. Would it not aid understanding if Microsoft explained what all it pushes on the stack. One of the most common hacking exploits on the net is called a stack overflow.

 

a.cs

using System;

class zzz

{

public static void Main()

{

yyy a = new yyy();

a.abc();

}

}

public class yyy

{

unsafe public void pqr( int *a)

{

Console.WriteLine("{0} {1} {2}",a[0],a[1],a[2]);

}

unsafe public void abc()

{

int [] a = new int[3];

a[0] = 10; a[1] = 2; a[2] = 30;

pqr(a);

}

}

 

Compiler Error

a.cs(20,1): error CS1502: The best overloaded method match for 'yyy.pqr(int*)' has some invalid arguments

a.cs(20,5): error CS1503: Argument '1': cannot convert from 'int[]' to 'int*'

 

We were trying to pass an array as a parameter to a function. Unfortunately C# has some other views.

 

 

a.cs

using System;

class zzz

{

public static void Main()

{

yyy a = new yyy();

a.abc();

}

}

public class yyy

{

unsafe public void pqr( int *a)

{

Console.WriteLine("{0} {1} {2}",a[0],a[1],a[2]);

}

unsafe public void abc()

{

int [] a = new int[3];

a[0] = 10; a[1] = 2; a[2] = 30;

fixed ( int *i = a) pqr(i);

}

}

 

Output

10 2 30

 

The keyword fixed removed the error. An array in memory can be moved around by C#. We would like C# to keep it fixed in memory. The keyword fixed guarantees that C# will not move it around in RAM for the duration of the program. The only reason C# moves objects in memory is to speed up execution of the program. The keyword fixed as part of syntax wants the () .Within them you can create a variable in our case i which we initialize to a C# object, an array a. The scope or lifetime of this array is in the next statement i.e. the invocation of function pqr. In case you have more lines you can use the {}. Even though a now is a pointer to an int, the notation a[0] refers to the first member. The name of an array tells us where the array starts in memory.

a.cs

using System;

class zzz

{

public static void Main()

{

yyy a = new yyy();

a.abc();

}

}

public class yyy

{

unsafe public void pqr( int *a)

{

Console.WriteLine("{0} {1} {2}",a[0],a[1],a[2]);

a[0] = 100 ; a[1] = 40; *(a+2) = 33;

}

unsafe public void abc()

{

int [] a = new int[3];

a[0] = 10; a[1] = 2; a[2] = 30;

fixed ( int *i = a) pqr(i);

Console.WriteLine("{0} {1} {2}",a[0],a[1],a[2]);

}

}

 

Output

10 2 30

100 40 33

 

Changing anything through a pointer changes the original. Whenever we write a[1]= , we are changing the first member of the original array. a[1] is only a notation, it actually get converted to *(a+1). Thus we are changing the original members of the array. Also the array grows upward in memory.

 

a.cs

using System;

class zzz

{

public static void Main()

{

yyy a = new yyy();

a.abc();

}

}

public class yyy

{

unsafe public void pqr( int *a)

{

Console.WriteLine("{0} {1} {2}",a[0],a[1],a[2]);

for ( int i = 0 ; i<= 2000; i++)

*(a+i) = i*10;

}

unsafe public void abc()

{

int [] a = new int[3];

a[0] = 10; a[1] = 2; a[2] = 30;

fixed ( int *i = a) pqr(i);

Console.WriteLine("{0} {1} {2}",a[0],a[1],a[2]);

}

}

 

Please do not run the above program as the error would be unpredictable. The reason being that we are writing beyond the bounds of the array. The array a created in the function abc has only 12 memory locations allocated for it. We are writing the next 2000 * 4, 8000 memory locations. The error on your machine will be very different form another machine. This unpredictability is why pointer errors are hard to catch, and your boss would thus not like you to use a pointer while programming.

 

a.cs

using System;

class zzz

{

public static void Main()

{

yyy a = new yyy();

a.abc();

}

}

struct aaa

{

public int i,j;

}

class yyy

{

unsafe public void abc()

{

aaa *a;aaa b;

b = new aaa();

a = (aaa *)b;

}

}

 

Compiler Error

a.cs(20,10): error CS0030: Cannot convert type 'aaa' to 'aaa*'

 

aaa is a structure with two members i and j. new aaa() allocates memory for i and j. new aaa() and b are of the same data type ie aaa. When we try to equate a and b, C# canot convert a pointer to a struct/class to the object itself. Remember new returns not a pointer to an object but the object itself.

 

a.cs

using System;

class zzz

{

public static void Main()

{

yyy a = new yyy();

a.abc();

}

}

struct aaa

{

public int i,j;

}

class yyy

{

unsafe public void abc()

{

aaa *a;aaa b;

b = new aaa();

a = &b;

int *x;

x = &(a->i);

int *y;

y = &(a->j);

Console.WriteLine((int) a + " " + (int)x + " " + (int) y);

}

}

 

Output

1243468 1243468 1243472

 

We need to take the address of where b begins in memory by using the & and equate that to a. The address of an object like aaa is of the data type aaa *. As a now is a pointer to a structure, we have to use a different syntax to display the members of the structure. The new way is to use the -> operator. Thus a->i refers to the member i of the structure that looks like aaa. The variable x now stores the address of where the first member i begins in memory. Obviously the address of the first member i and the address of the structure will be the same and the second member will be stored 4 later.

 

a.cs

using System;

class zzz

{

public static void Main()

{

yyy a = new yyy();

a.abc();

}

}

struct aaa

{

public int i,j;

}

class yyy

{

unsafe public void abc()

{

aaa *a;aaa b;

b = new aaa();

a = &b;

Console.WriteLine((int) a);

b = new aaa();

a = &b;

Console.WriteLine((int) a);

}

}

 

Output

1243468

1243468

 

We have one object b that we have initialized twice with new. Each time we get the same memory location as the address of b in memory will remain the same.

 

Unions

 

a.cs

using System.Runtime.InteropServices;

class zzz

{

unsafe public static void Main()

{

yyy a = new yyy();

a.i = 65536+512+3;

System.Console.WriteLine(a.i + " " + a.j + " " + a.k);

int *i1;short *j1;byte *k1;

i1 = &a.i; j1 = &a.j ; k1 = &a.k;

System.Console.WriteLine((int)i1 + " " + (int) j1 + " " + (int) k1);

System.Console.WriteLine(sizeof(yyy));

}

}

[StructLayout(LayoutKind.Explicit)]

struct yyy

{

[FieldOffset(0)] public int i;

[FieldOffset(0)] public short j;

[FieldOffset(0)] public byte k;

}

 

Output

66051 515 3

1243476 1243476 1243476

4

 

 

Let us understand what a union is all about. We are printing the addresses of i, j and k and lo and behold they are all the same. In a union all the members begin at the same place and that is why their addresses were similar. If we understand pointers, understanding unions then becomes a piece of cake as we can visually see the addresses.

 

a.cs

using System.Runtime.InteropServices;

class zzz

{

unsafe public static void Main()

{

yyy a = new yyy();

a.i = 65536+512+3;

System.Console.WriteLine(a.i + " " + a.j + " " + a.k);

int *i1;short *j1;byte *k1;

i1 = &a.i; j1 = &a.j ; k1 = &a.k;

System.Console.WriteLine((int)i1 + " " + (int) j1 + " " + (int) k1);

System.Console.WriteLine(sizeof(yyy));

}

}

[StructLayout(LayoutKind.Explicit)]

struct yyy

{

[FieldOffset(0)]

public int i;

[FieldOffset(0)]

public short j;

[FieldOffset(10)]

public byte k;

}

 

Output

66051 515 0

1243468 1243468 1243478

11

 

a.cs

using System;

class zzz

{

public static void Main()

{

yyy a = new yyy();

a.abc();

}

}

class yyy

{

unsafe public void abc()

{

short i = 512+3;

byte *j;

j = (byte *)&i;

Console.WriteLine((int)j + " " + i );

*j = 1;

Console.WriteLine(i);

j++;

Console.WriteLine((int)j);

*j=1;

Console.WriteLine(i);

j++;

Console.WriteLine((int)j);

*j=10;

Console.WriteLine(i);

}

}

 

Output

1243468 515

513

1243469

257

1243470

257

 

 

 

 

 

j is a pointer to a byte. From the output, we can see that i begins at 1243468. i is of data type short which means it takes 2 bytes. j now contains the the address value of i i.e. 1243468. The first line proves it. The statement *j=1 will change the bottom byte value to 1.j++ will then increment the address value by 1, hence *j=1 will now change the top byte to 1. The last j++ has no effect because the scope of short is only 2 btes. Hence we see no change in the value of i.

 

Here, in place of the data type short, we have given an int.

 

a.cs

using System;

class zzz {

public static void Main()

{

yyy a = new yyy();

a.abc();

}

}

class yyy {

unsafe public void abc()

{

int i = 512+3;

byte *j;

j = (byte *)&i;

Console.WriteLine((int)j + " " + i );

*j = 1;

Console.WriteLine(i);

j++;

Console.WriteLine((int)j);

*j=1;

Console.WriteLine(i);

j++;

Console.WriteLine((int)j);

*j=10;

Console.WriteLine(i);

}

}

 

 

Output

1243468 515

513

1243469

257

1243470

655617

 

The last j++ in the series will touch the third byte, there by replacing the 1 to 10. Hence the output changes dramatically.