Unsafe code can use pointers and call into memory directly (C# specification clause 24).
The runtime does not check it, so errors can corrupt memory. Compiling it needs the
AllowUnsafeBlocks project property, or the -unsafe option. It is
off by default.
Unsafe contexts
The unsafe modifier makes its target an unsafe context (clause 24.2). Pointer
types and the pointer operators may be used only inside one.
unsafe class Buffer { } // a whole type
unsafe void Copy(byte* src) { } // a member
void Run()
{
unsafe // a block
{
int x = 1;
int* p = &x;
}
}
Before C# 13, iterators and async methods could not contain unsafe code. Since C# 13
they can, provided the unsafe code contains no yield return or
await. See the C# 15 preview for changes in
progress.
Pointer types
data_pointer_type:
unmanaged_type *
void *
A pointer can point only to an unmanaged type (clause 8.8): a numeric type,
char, bool, an enum, a pointer, or a struct containing only
unmanaged types. A pointer type is not an object, and cannot be boxed.
In a declaration, the * goes with the type:
int* a, b; declares two pointers, unlike C and C++.
| Operator | Meaning | Clause |
|---|---|---|
*p | The value pointed to | 24.6.2 |
p->x | A member of the struct pointed to | 24.6.3 |
p[i] | The element i places on | 24.6.4 |
&x | The address of a variable | 24.6.5 |
p++, p-- | Move by one element | 24.6.6 |
p + i, p - i, p - q | Arithmetic, in units of the element size | 24.6.7 |
==, !=, <, >, <=, >= | Compare addresses | 24.6.8 |
Conversions exist between pointer types, void* and integer types, and
they are explicit, apart from a pointer to void*, and
null to a pointer (clause 24.5).
sizeof
sizeof(T) gives a size in bytes (clause 24.6.9). The result is
constant for the built-in types, and these do not need an unsafe context. For other
unmanaged types it requires one.
| Type | Size |
|---|---|
sbyte, byte, bool | 1 |
short, ushort, char | 2 |
int, uint, float | 4 |
long, ulong, double | 8 |
decimal | 16 |
The fixed statement
The garbage collector can move objects. fixed pins an object while a pointer
into it is in use (clause 24.7).
int[] data = { 1, 2, 3 };
string text = "hello";
unsafe
{
fixed (int* p = data)
fixed (char* c = text)
{
*p = 9;
}
fixed (int* q = &data[1]) { }
}
The target may be an array, a string, the address of a field or array element, or a type
with a GetPinnableReference method (C# 7.3). The pointer is read-only, and
the object stays pinned only for the statement. A local variable is already fixed and
needs no fixed (clause 24.4).
stackalloc
stackalloc allocates a block on the stack (clauses 12.8.22 and 24.9). The
memory is freed when the method returns. The contents are undefined unless an initialiser
is given.
Span<int> a = stackalloc int[8]; // safe, C# 7.2
Span<int> b = stackalloc int[] { 1, 2, 3 }; // initialiser, C# 7.3
unsafe { int* c = stackalloc int[8]; } // pointer form
int n = Sum(stackalloc int[] { 1, 2 }); // in a nested expression, C# 8
Assigning to Span<T> or ReadOnlySpan<T> needs no unsafe
context. stackalloc cannot be used in a catch or
finally block. A large or repeated allocation can overflow the stack, which
cannot be caught.
Fixed-size buffers
A struct can hold an inline array of a primitive type with a fixed-size buffer (clause 24.8). It needs an unsafe context.
unsafe struct Packet
{
public fixed byte Data[64];
public fixed int Sums[4];
}
The element type must be one of bool, byte, char,
short, int, long, sbyte,
ushort, uint, ulong, float or
double. Since C# 7.3 a buffer can be indexed without pinning in some cases.
Inline arrays are a safe
alternative.
Function pointers
A function pointer (C# 9, clause 24.3.3) is a pointer to a method. It calls the method
without a delegate object. It uses delegate* syntax, and the last type
is the return type.
unsafe
{
static int Twice(int x) => x * 2;
delegate*<int, int> f = &Twice; // managed
int r = f(21);
delegate* unmanaged[Cdecl]<int, int> g = null; // unmanaged, with a calling convention
}
A pointer is managed by default, or unmanaged with optional
conventions such as Cdecl, Stdcall, Thiscall and
Fastcall. Only static methods can be converted to a function pointer, with
&.
The memory safety preview
The .NET 11 SDK includes a preview, under C# 15, of a redesign of memory safety. It ties
unsafe to operations that actually access unmanaged memory. It needs
<LangVersion>preview</LangVersion>, and the updated rules also need the
updated-memory-safety-rules compiler feature (feature specification
Unsafe evolution).
- Under the preview language version, declaring a pointer, taking an address with
&, afixedstatement, convertingstackallocto a pointer, andsizeofon an unmanaged type no longer need an unsafe context. - Dereferencing a pointer,
p->member,p[i]and calling a function pointer still do. unsafe(expression)gives one expression an unsafe context, in places such as a field initialiser where a block cannot go.- Under the updated rules,
unsafeon a member marks it as requires-unsafe: its callers must use it from an unsafe context. The contextual keywordsafemarksexternmembers and fields in explicit or extended layout types as safe.