C# supports direct memory manipulation via pointers within blocks of code marked unsafe. 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 in a C# program, there is a corresponding C# pointer type named V*. A pointer instance holds the address of a value. That value is considered to be of type V, but pointer types can be (unsafely) cast to any other pointer type. Table 2.3 summarizes the principal pointer operators supported by the C# language.
Methods, statement blocks, or single statements can be marked with
the unsafe
keyword to perform C++-style pointer
operations on memory. Here is an example that uses pointers with a
managed object:
unsafe void RedFilter(int[,] bitmap) { const int length = bitmap.Length; fixed (int* b = bitmap) { int* p = b; for(int i = 0; i < length; i++) *p++ &= 0xFF; } }
Unsafe code typically runs faster than a corresponding safe implementation, which in this case would have required a nested loop with array indexing and bounds checking. An unsafe C# method can be faster than calling an external C function too, since there is no overhead associated with leaving the managed execution environment.
fixed
([
value type
|
void
] * name
=
[ &]?
expression
)
|
statement-block
|
The fixed
statement is required to pin a
managed object, such as the bitmap in the previous pointer example.
During the execution of a program, many objects are allocated and
deallocated from the heap. In order to avoid the unnecessary waste or
fragmentation of memory, the garbage collector moves objects around.
Pointing to an object would be futile if its address can change while
referencing it, so the fixed
statement tells the
garbage collector to pin the object and not move it around. This can
impact the efficiency of the runtime, so fixed
blocks should be used only briefly, and preferably heap allocation
should be avoided within the fixed
block.
C# returns a pointer only from a value type, never directly from a reference type. Arrays and strings are an exception to this, but only syntactically, since they actually return a pointer to their first element (which must be a value type), rather than the objects themselves.
Value types declared inline within reference types require the reference type to be pinned, as follows:
class Test { int x; static void Main( ) { Test test = new Test( ); unsafe { 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 in
which it is used, just as with other local variables. The block may
use []
indexing but is purely a value type with
no additional self-describing information or bounds checking an array
provides;
int* a = stackalloc int [10]; for (int i = 0; i < 10; ++i) Console.WriteLine(a[i]); // print raw memory
3.138.123.106