C# supports direct memory manipulation via pointers within blocks of code
marked unsafe and compiled with the /unsafe
compiler option. Pointer types are
primarily useful for interoperability with C APIs, but may also
be used for accessing memory outside the managed heap or for
performance-critical hotspots.
For every value type or pointer type V, there is a corresponding pointer type V*. A pointer instance holds the address of a variable. Pointer types can be (unsafely) cast to any other pointer type. The main pointer operators are:
Operator | Meaning |
---|---|
| The address-of operator returns a pointer to the address of a variable. |
| The dereference operator returns the variable at the address of a pointer. |
| The
pointer-to-member operator is a syntactic
shortcut, in which |
By marking a type, type member, or statement block with the
unsafe
keyword, you’re permitted to
use pointer types and perform C++-style pointer operations on memory
within that scope. Here is an example of using pointers to quickly
process a bitmap:
unsafe void BlueFilter (int[,] bitmap) { int length = bitmap.Length; fixed (int* b = bitmap) { int* p = b; for (int i = 0; i < length; i++) *p++ &= 0xFF; } }
Unsafe code can run faster than a corresponding safe implementation. In this case, the code would have required a nested loop with array indexing and bounds checking. An unsafe C# method may also be faster than calling an external C function, since there is no overhead associated with leaving the managed execution environment.
The fixed
statement is
required to pin a managed object, such as the bitmap in the previous
example. During the execution of a program, many objects are allocated
and deallocated from the heap. In order to avoid unnecessary waste or
fragmentation of memory, the garbage collector moves objects around.
Pointing to an object is futile if
its address could change while referencing it, so the fixed
statement tells the garbage collector to
“pin” the object and not move it around. This may have an impact on the
efficiency of the runtime, so fixed blocks should be used only briefly,
and heap allocation should be avoided within the fixed block.
Within a fixed
statement, you
can get a pointer to a value type, an array of value types, or a string.
In the case of arrays and strings, the pointer will actually point to
the first element, which is a value type.
Value types declared inline within reference types require the reference type to be pinned, as follows:
class Test { int x; unsafe static void Main() { Test test = new Test(); fixed (int* p = &test.x) // Pins test { *p = 9; } System.Console.WriteLine (test.x); } }
In addition to the &
and *
operators, C# also provides the
C++-style ->
operator, which can be used on
structs:
struct Test { int x; unsafe static void Main() { Test test = new Test(); Test* p = &test; p->x = 9; System.Console.WriteLine (test.x); } }
Memory can be allocated in a block on the stack explicitly
using the stackalloc
keyword. Since
it is allocated on the stack, its lifetime is limited to the execution
of the method, just as with any other local variable. The block may
use the []
operator to index into
memory:
int* a = stackalloc int [10]; for (int i = 0; i < 10; ++i) Console.WriteLine (a[i]); // Print raw memory
Memory can be allocated in a block within a struct using the
fixed
keyword:
unsafe struct UnsafeUnicodeString
{
public short Length;
public fixed
byte Buffer[30];
}
unsafe class UnsafeClass
{
UnsafeUnicodeString uus;
public UnsafeClass (string s)
{
uus.Length = (short)s.Length;
fixed (byte* p = uus.Buffer)
for (int i = 0; i < s.Length; i++)
p[i] = (byte) s[i];
}
}
The fixed
keyword is also
used in this example to pin the object on the heap that contains the
buffer (which will be the instance of UnsafeClass
).
A void pointer (void*
) makes no assumptions about the type of
the underlying data and is useful for functions that deal with raw
memory. An implicit conversion exists from any pointer type to void*
. A void*
cannot be dereferenced, and arithmetic
operations cannot be performed on void pointers. For example:
unsafe static void Main() { short[] a = {1,1,2,3,5,8,13,21,34,55}; fixed (short* p = a) { //sizeof returns size of value-type in bytes Zap (p, a.Length * sizeof (short)); } foreach (short x in a) System.Console.WriteLine (x); // Prints all zeros } unsafe static void Zap (void* memory, int byteCount) { byte* b = (byte*) memory; for (int i = 0; i < byteCount; i++) *b++ = 0; }