| title | Unsafe code, pointers to data, and function pointers | |||||||
|---|---|---|---|---|---|---|---|---|
| description | Learn about unsafe code, pointers, and function pointers. C# requires you to declare an unsafe context to use these features to directly manipulate memory or function pointers (unmanaged delegates). | |||||||
| ms.date | 01/16/2026 | |||||||
| f1_keywords |
|
|||||||
| helpviewer_keywords |
|
Most of the C# code you write is verifiably safe code. Verifiably safe code means that .NET tools can verify that the code is safe. In general, safe code doesn't directly access memory by using pointers. It also doesn't allocate raw memory. It creates managed objects instead.
[!INCLUDEcsharp-version-note]
C# supports an unsafe context, in which you can write unverifiable code. In an unsafe context, code can use pointers, allocate and free blocks of memory, and call methods by using function pointers. Unsafe code in C# isn't necessarily dangerous; it's just code whose safety can't be verified.
Unsafe code has the following properties:
- You can define methods, types, and code blocks as unsafe.
- In some cases, unsafe code can increase an application's performance by enabling direct memory access through pointers to avoid array bounds checks.
- You use unsafe code to call native functions that require pointers.
- Using unsafe code introduces security and stability risks.
- You must add the AllowUnsafeBlocks compiler option to compile the code that contains unsafe blocks.
For information about best practices for unsafe code in C#, see Unsafe code best practices.
In an unsafe context, a type can be a pointer type, in addition to a value type or a reference type. A pointer type declaration takes one of the following forms:
type* identifier;
void* identifier; //allowed but not recommendedThe type you specify before the * in a pointer type is the referent type.
Pointer types don't inherit from object, and no conversions exist between pointer types and object. Also, boxing and unboxing don't support pointers. However, you can convert between different pointer types and between pointer types and integral types.
When you declare multiple pointers in the same declaration, write the asterisk (*) together with the underlying type only. It isn't used as a prefix to each pointer name. For example:
int* p1, p2, p3; // Ok
int *p1, *p2, *p3; // Invalid in C#The garbage collector doesn't keep track of whether an object is being pointed to by any pointer types. If the referent is an object in the managed heap (including local variables captured by lambda expressions or anonymous delegates), you must pin the object for as long as the pointer is used.
The value of the pointer variable of type MyType* is the address of a variable of type MyType. The following are examples of pointer type declarations:
int* p:pis a pointer to an integer.int** p:pis a pointer to a pointer to an integer.int*[] p:pis a single-dimensional array of pointers to integers.char* p:pis a pointer to a char.void* p:pis a pointer to an unknown type.
You can use the pointer indirection operator * to access the contents at the location pointed to by the pointer variable. For example, consider the following declaration:
int* myVariable;The expression *myVariable denotes the int variable found at the address contained in myVariable.
There are several examples of pointers in the articles on the fixed statement. The following example uses the unsafe keyword and the fixed statement, and shows how to increment an interior pointer. You can paste this code into the Main function of a console application to run it. These examples must be compiled with the AllowUnsafeBlocks compiler option set.
:::code language="csharp" source="snippets/unsafe-code/FixedKeywordExamples.cs" ID="5":::
You can't apply the indirection operator to a pointer of type void*. However, you can use a cast to convert a void pointer to any other pointer type, and vice versa.
A pointer can be null. Applying the indirection operator to a null pointer causes an implementation-defined behavior.
Passing pointers between methods can cause undefined behavior. Consider a method that returns a pointer to a local variable through an in, out, or ref parameter or as the function result. If the pointer was set in a fixed block, the variable to which it points might no longer be fixed.
The following table lists the operators and statements that can operate on pointers in an unsafe context:
| Operator/Statement | Use |
|---|---|
* |
Performs pointer indirection. |
-> |
Accesses a member of a struct through a pointer. |
[] |
Indexes a pointer. |
& |
Obtains the address of a variable. |
++ and -- |
Increments and decrements pointers. |
+ and - |
Performs pointer arithmetic. |
==, !=, <, >, <=, and >= |
Compares pointers. |
stackalloc |
Allocates memory on the stack. |
fixed statement |
Temporarily fixes a variable so that its address can be found. |
For more information about pointer-related operators, see Pointer-related operators.
Any pointer type can be implicitly converted to a void* type. Any pointer type can be assigned the value null. You can explicitly convert any pointer type to any other pointer type using a cast expression. You can also convert any integral type to a pointer type, or any pointer type to an integral type. These conversions require an explicit cast.
The following example converts an int* to a byte*. Notice that the pointer points to the lowest addressed byte of the variable. When you successively increment the result, up to the size of int (4 bytes), you can display the remaining bytes of the variable.
:::code language="csharp" source="snippets/unsafe-code/Conversions.cs" ID="Conversion":::
Use the fixed keyword to create a buffer with a fixed-size array in a data structure. Fixed-size buffers are useful when you write methods that interoperate with data sources from other languages or platforms. The fixed-size buffer can take any attributes or modifiers that are allowed for regular struct members. The only restriction is that the array type must be bool, byte, char, short, int, long, sbyte, ushort, uint, ulong, float, or double.
private fixed char name[30];In safe code, a C# struct that contains an array doesn't contain the array elements. The struct contains a reference to the elements instead. You can embed an array of fixed size in a struct when it's used in an unsafe code block.
The size of the following struct doesn't depend on the number of elements in the array, since pathName is a reference:
:::code language="csharp" source="snippets/unsafe-code/FixedKeywordExamples.cs" ID="6":::
A struct can contain an embedded array in unsafe code. In the following example, the fixedBuffer array has a fixed size. You use a fixed statement to get a pointer to the first element. You access the elements of the array through this pointer. The fixed statement pins the fixedBuffer instance field to a specific location in memory.
:::code language="csharp" source="snippets/unsafe-code/FixedKeywordExamples.cs" ID="7":::
The size of the 128 element char array is 256 bytes. Fixed-size char buffers always take 2 bytes per character, regardless of the encoding. This array size is the same even when char buffers are marshaled to API methods or structs with CharSet = CharSet.Auto or CharSet = CharSet.Ansi. For more information, see xref:System.Runtime.InteropServices.CharSet.
The preceding example demonstrates accessing fixed fields without pinning. Another common fixed-size array is the bool array. The elements in a bool array are always 1 byte in size. bool arrays aren't appropriate for creating bit arrays or buffers.
Fixed-size buffers are compiled with the xref:System.Runtime.CompilerServices.UnsafeValueTypeAttribute?displayProperty=nameWithType, which instructs the common language runtime (CLR) that a type contains an unmanaged array that can potentially overflow. Memory allocated by using stackalloc also automatically enables buffer overrun detection features in the CLR. The preceding example shows how a fixed-size buffer could exist in an unsafe struct.
internal unsafe struct Buffer
{
public fixed char fixedBuffer[128];
}The compiler-generated C# for Buffer is attributed as follows:
internal struct Buffer
{
[StructLayout(LayoutKind.Sequential, Size = 256)]
[CompilerGenerated]
[UnsafeValueType]
public struct <fixedBuffer>e__FixedBuffer
{
public char FixedElementField;
}
[FixedBuffer(typeof(char), 128)]
public <fixedBuffer>e__FixedBuffer fixedBuffer;
}Fixed-size buffers differ from regular arrays in the following ways:
- You can only use them in an
unsafecontext. - They can only be instance fields of structs.
- They're always vectors, or one-dimensional arrays.
- The declaration must include the length, such as
fixed char id[8]. You can't usefixed char id[].
The following example uses pointers to copy bytes from one array to another.
This example uses the unsafe keyword, which enables you to use pointers in the Copy method. The fixed statement declares pointers to the source and destination arrays. The fixed statement pins the location of the source and destination arrays in memory so that garbage collection doesn't move the arrays. The fixed block pins the memory blocks for the arrays in the scope of the block. Because the Copy method in this example uses the unsafe keyword, you must compile it by using the AllowUnsafeBlocks compiler option.
This example accesses the elements of both arrays by using indices rather than a second unmanaged pointer. The declaration of the pSource and pTarget pointers pins the arrays.
:::code language="csharp" source="snippets/unsafe-code/FixedKeywordExamples.cs" ID="8":::
C# provides delegate types to define safe function pointer objects. Invoking a delegate involves instantiating a type derived from xref:System.Delegate?displayProperty=nameWithType and making a virtual method call to its Invoke method. This virtual call uses the callvirt IL instruction. In performance critical code paths, using the calli IL instruction is more efficient.
You can define a function pointer by using the delegate* syntax. The compiler calls the function by using the calli instruction rather than instantiating a delegate object and calling Invoke. The following code declares two methods that use a delegate or a delegate* to combine two objects of the same type. The first method uses a xref:System.Func%603?displayProperty=nameWithType delegate type. The second method uses a delegate* declaration with the same parameters and return type:
:::code language="csharp" source="snippets/unsafe-code/FunctionPointers.cs" ID="UseDelegateOrPointer":::
The following code shows how you declare a static local function and invoke the UnsafeCombine method by using a pointer to that local function:
:::code language="csharp" source="snippets/unsafe-code/FunctionPointers.cs" ID="InvokeViaFunctionPointer":::
The preceding code illustrates several of the rules on the function accessed as a function pointer:
- You can only declare function pointers in an
unsafecontext. - You can only call methods that take a
delegate*(or return adelegate*) in anunsafecontext. - The
&operator to obtain the address of a function is allowed only onstaticfunctions. This rule applies to both member functions and local functions.
The syntax has parallels with declaring delegate types and using pointers. The * suffix on delegate indicates the declaration is a function pointer. The & when assigning a method group to a function pointer indicates the operation takes the address of the method.
You can specify the calling convention for a delegate* by using the keywords managed and unmanaged. In addition, for unmanaged function pointers, you can specify the calling convention. The following declarations show examples of each. The first declaration uses the managed calling convention, which is the default. The next four use an unmanaged calling convention. Each specifies one of the ECMA 335 calling conventions: Cdecl, Stdcall, Fastcall, or Thiscall. The last declaration uses the unmanaged calling convention, instructing the CLR to pick the default calling convention for the platform. The CLR chooses the calling convention at run time.
:::code language="csharp" source="snippets/unsafe-code/FunctionPointers.cs" ID="UnmanagedFunctionPointers":::
You can learn more about function pointers in the Function pointer feature spec.
For more information, see the Unsafe code chapter of the C# language specification.