unsafe C# code
In all the code I wrote for VSTF I never used unsafe C# code. However, recently I was writing a tool which needed some unsafe code (sizeof in particular).
While writing the code I thought of some quick points to remember while using unsafe code.
Do not use redundant unsafe context
In C#2.0 you no longer need to use unsafe context while applying sizeof to predefined types. This means the following code compiles without any unsafe context.
class MyClass
{
static public void Foo()
{
Console.WriteLine(sizeof(int));
Console.WriteLine(sizeof(byte));
}
}
Limit the scope of the unsafe context
Locate the minimal scope of unsafe code and apply either the unsafe modifier or unsafe statement over that minimal scope only.
The following is bad, because other methods in the class are also included in the unsafe context and unintentinally unsafe code may creep into them.
unsafe class MyClass
{
static public void Foo()
{
Console.WriteLine(sizeof(MyStruct));
}
}
The following is better because only required methods are marked as unsafe.
class MyClass
{
unsafe static public void Foo()
{
Console.WriteLine(sizeof(MyStruct));
}
}
The following is the best option if you have unsafe code only in limited places, as this gives the unsafe context only to the minimal scope (statement level).
class MyClass
{
static public void Foo()
{
unsafe
{
Console.WriteLine(sizeof(MyStruct)); }
}
}
Be careful about subtle syntax difference
Most commonly these unsafe features get used by C/C++ programmers moving into C#. The syntax looks deceptively similar to C/C++ but sometimes varies. The following statment is valid C/C++ and valid C# as well.
int * a, b;
However, in C/C++ it means that a is a pointer and b is not but in C# a and b are both pointers to ints. So in C# * should be written with type as in int* and not with the pointer name.
Becareful about what pointers you return
This is equally true for C/C++. You must ensure that you are not returning a pointer (as return value or ref/out arguments) that points to a local variable. Once the function returns the stack is popped and the local variables are gone. So the returned pointers become dangling pointers if they pointed to some local variables
You can't point to everything
Everything that resides in the manages heap can be moved around (relocated) by the garbage-collector and therefore it is not possible to have pointers to it directly. You can have pointers only to fixed variables. Fixed variables include stuff that is on the stack like local variables and value-type function-parameters. You can also take pointers to variables on the heap after pinning them with a fixed expression
class MyClass
{
static int statNum = 50;
unsafe static public void Bar(int paramNum)
{
int localNum;
int* ap;
ap = &localNum; //local variable
ap = ¶mNum; //value-type argument
//pin so that the address don't change
fixed(int* apnew = &statNum)
{
Console.WriteLine(*apnew);
}
}
}
Avoid unsafe code and pointers
There was a reson for which pointers were excluded in core C#. Even though pointers make a language very powerfull they introduce complexity and pointer related bugs like dangling pointers, overrun, type-unsafety plague the software. C/C++ programmers have a habit of using unsafe context even when they are not required and this is a habit that may lead to trouble in the .NET world. Use unsafe code only when absolutely necessary....
Comments
- Anonymous
February 24, 2006
Good post, Abhinaba. Unsafe code is pretty useful for interop scenarios, where sometimes there is no convenient alternative. - Anonymous
February 24, 2006
Thanks and yes and interop defintely makes it into the absolutely necessary condition.... - Anonymous
February 24, 2006
If you are only looking for sizeof, best use Marshal.SizeOf(). - Anonymous
February 24, 2006
I was just waiting for someone to say this :)
There are differences between sizeof operator and Marshal.Sizeof. Try out the following
Console.WriteLine(sizeof(char));
Console.WriteLine(Marshal.SizeOf(typeof(char)));
and you'd see that the second statement prints out 1 instead of two. The explanation is in http://blogs.msdn.com/shawnfa/archive/2005/02/25/380592.aspx. Now you know why I used sizeof and not Marshal.SizeOf :) - Anonymous
January 16, 2007
Thanks. I never knew initializing multiple int*s in a single statement caused them all to be pointers...