/////////////////////////////////////////////////////////////////
C# programming guide
Unsafe code and pointers
/////////////////////////////////////////////////////////////////
Unsafe code and pointers (C# Programming Guide)
07/20/2015 2 minutes to read
To maintain type safety and security, C# does not support pointer arithmetic, by default. However, by using the unsafe keyword, you can define an unsafe context in which pointers can be used. For more information about pointers, see Pointer types.
Note
In the common language runtime (CLR), unsafe code is referred to as unverifiable code. Unsafe code in C# is not necessarily dangerous; it is just code whose safety cannot be verified by the CLR. The CLR will therefore only execute unsafe code if it is in a fully trusted assembly. If you use unsafe code, it is your responsibility to ensure that your code does not introduce security risks or pointer errors.
Unsafe code overview
Unsafe code has the following properties:
- Methods, types, and code blocks can be defined as unsafe.
- In some cases, unsafe code may increase an application's performance by removing array bounds checks.
- Unsafe code is required when you call native functions that require pointers.
- Using unsafe code introduces security and stability risks.
- The code that contains unsafe blocks must be compiled with the -unsafe compiler option.
Related sections
For more information, see:
C# language specification
For more information, see the Unsafe code topic of the C# language specification.
See also
////////////////////////////////////////////////////////////////////////////
Fixed Size Buffers (C# Programming Guide)
04/20/2018 2 minutes to read
In C#, you can use the fixed statement to create a buffer with a fixed size array in a data structure. Fixed size buffers are useful when you write methods that interop with data sources from other languages or platforms. The fixed array 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.
C#Copy
private fixed char name[30];
Remarks
In safe code, a C# struct that contains an array does not contain the array elements. Instead, the struct contains a reference to the elements. You can embed an array of fixed size in a struct when it is used in an unsafe code block.
Size of the following struct doesn't depend on the number of elements in the array, since pathName is a reference:
C#Copy
public struct PathArray
{
public char[] pathName;
private int reserved;
}
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 establish a pointer to the first element. You access the elements of the array through this pointer. The fixedstatement pins the fixedBuffer instance field to a specific location in memory.
C#Copy
internal unsafe struct MyBuffer
{
public fixed char fixedBuffer[128];
}
internal unsafe class MyClass
{
public MyBuffer myBuffer = default;
}
private static void AccessEmbeddedArray()
{
MyClass myC = new MyClass();
unsafe
{
// Pin the buffer to a fixed location in memory.
fixed (char* charPtr = myC.myBuffer.fixedBuffer)
{
*charPtr = 'A';
}
// Access safely through the index:
char c = myC.myBuffer.fixedBuffer[0];
Console.WriteLine(c);
// modify through the index:
myC.myBuffer.fixedBuffer[0] = 'B';
Console.WriteLine(myC.myBuffer.fixedBuffer[0]);
}
}
The size of the 128 element char array is 256 bytes. Fixed size char buffers always take two bytes per character, regardless of the encoding. This is true even when char buffers are marshaled to API methods or structs with CharSet = CharSet.Auto or CharSet = CharSet.Ansi. For more information, see CharSet.
The preceding example demonstrates accessing fixed fields without pinning, which is available starting with C# 7.3.
Another common fixed-size array is the bool array. The elements in a bool array are always one byte in size. bool arrays are not appropriate for creating bit arrays or buffers.
Note
Except for memory created by using stackalloc, the C# compiler and the common language runtime (CLR) do not perform any security buffer overrun checks. As with all unsafe code, use caution.
Unsafe buffers differ from regular arrays in the following ways:
- You can only use unsafe buffers in an unsafe context.
- Unsafe buffers are always vectors, or one-dimensional arrays.
- The declaration of the array should include a count, such as char id[8]. You cannot use char id[].
- Unsafe buffers can only be instance fields of structs in an unsafe context.
See also
////////////////////////////////////////////////////////////////////////////////////
Pointer types (C# Programming Guide)
04/20/2018 3 minutes to read
In an unsafe context, a type may be a pointer type, a value type, or a reference type. A pointer type declaration takes one of the following forms:
C#Copy
type* identifier;
void* identifier; //allowed but not recommended
The type specified before the * in a pointer type is called the referent type. Only an unmanaged type can be a referent type.
Pointer types do not inherit from object and no conversions exist between pointer types and object. Also, boxing and unboxing do not 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, the asterisk (*) is written together with the underlying type only; it is not used as a prefix to each pointer name. For example:
C#Copy
int* p1, p2, p3; // Ok
int *p1, *p2, *p3; // Invalid in C#
A pointer cannot point to a reference or to a struct that contains references, because an object reference can be garbage collected even if a pointer is pointing to it. The garbage collector does not keep track of whether an object is being pointed to by any pointer types.
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:
TABLE 1 |
|
Example |
Description |
int* p |
p is a pointer to an integer. |
int** p |
p is a pointer to a pointer to an integer. |
int*[] p |
p is a single-dimensional array of pointers to integers. |
char* p |
p is a pointer to a char. |
void* p |
p is a pointer to an unknown type. |
The pointer indirection operator * can be used to access the contents at the location pointed to by the pointer variable. For example, consider the following declaration:
C#Copy
int* myVariable;
The expression *myVariable denotes the int variable found at the address contained in myVariable.
There are several examples of pointers in the topics fixed Statement and Pointer Conversions. The following example uses the unsafekeyword 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 -unsafe compiler option set.
C#Copy
// Normal pointer to an object.
int[] a = new int[5] { 10, 20, 30, 40, 50 };
// Must be in unsafe code to use interior pointers.
unsafe
{
// Must pin object on heap so that it doesn't move while using interior pointers.
fixed (int* p = &a[0])
{
// p is pinned as well as object, so create another pointer to show incrementing it.
int* p2 = p;
Console.WriteLine(*p2);
// Incrementing p2 bumps the pointer by four bytes due to its type ...
p2 += 1;
Console.WriteLine(*p2);
p2 += 1;
Console.WriteLine(*p2);
Console.WriteLine("--------");
Console.WriteLine(*p);
// Dereferencing p and incrementing changes the value of a[0] ...
*p += 1;
Console.WriteLine(*p);
*p += 1;
Console.WriteLine(*p);
}
}
Console.WriteLine("--------");
Console.WriteLine(a[0]);
/*
Output:
10
20
30
--------
10
11
12
--------
12
*/
You cannot 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 may no longer be fixed.
The following table lists the operators and statements that can operate on pointers in an unsafe context:
TABLE 2 |
|
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. |
Allocates memory on the stack. |
|
Temporarily fixes a variable so that its address may be found. |
For more information about pointer related operators, see Pointer related operators.
C# language specification
For more information, see the Pointer types section of the C# language specification.
See also
- C# Programming Guide
- Unsafe Code and Pointers
- Pointer Conversions
- Reference types
- Value types
- unsafe
////////////////////////////////////////////////////////////////////////////////////
Pointer Conversions (C# Programming Guide)
07/20/2015 2 minutes to read
The following table shows the predefined implicit pointer conversions. Implicit conversions might occur in many situations, including method invoking and assignment statements.
Implicit pointer conversions
TABLE 1 |
|
From |
To |
Any pointer type |
void* |
null |
Any pointer type |
Explicit pointer conversion is used to perform conversions, for which there is no implicit conversion, by using a cast expression. The following table shows these conversions.
Explicit pointer conversions
TABLE 2 |
|
From |
To |
Any pointer type |
Any other pointer type |
sbyte, byte, short, ushort, int, uint, long, or ulong |
Any pointer type |
Any pointer type |
sbyte, byte, short, ushort, int, uint, long, or ulong |
Example
In the following example, a pointer to int is converted to a pointer to 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.
C#Copy
// compile with: -unsafe
C#Copy
class ClassConvert
{
static void Main()
{
int number = 1024;
unsafe
{
// Convert to byte:
byte* p = (byte*)&number;
System.Console.Write("The 4 bytes of the integer:");
// Display the 4 bytes of the int variable:
for (int i = 0 ; i < sizeof(int) ; ++i)
{
System.Console.Write(" {0:X2}", *p);
// Increment the pointer:
p++;
}
System.Console.WriteLine();
System.Console.WriteLine("The value of the integer: {0}", number);
// Keep the console window open in debug mode.
System.Console.WriteLine("Press any key to exit.");
System.Console.ReadKey();
}
}
}
/* Output:
The 4 bytes of the integer: 00 04 00 00
The value of the integer: 1024
*/
See also
///////////////////////////////////////////////////////////////////////////////////
How to use pointers to copy an array of bytes (C# Programming Guide)
04/20/2018 3 minutes to read
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 is used to declare pointers to the source and destination arrays. The fixed statement pins the location of the source and destination arrays in memory so that they will not be moved by garbage collection. The memory blocks for the arrays are unpinned when the fixed block is completed. Because the Copy method in this example uses the unsafe keyword, it must be compiled with the -unsafe compiler option.
This example accesses the elements of both arrays using indices rather than a second unmanaged pointer. The declaration of the pSource and pTarget pointers pins the arrays. This feature is available starting with C# 7.3.
Example
C#Copy
static unsafe void Copy(byte[] source, int sourceOffset, byte[] target,
int targetOffset, int count)
{
// If either array is not instantiated, you cannot complete the copy.
if ((source == null) || (target == null))
{
throw new System.ArgumentException();
}
// If either offset, or the number of bytes to copy, is negative, you
// cannot complete the copy.
if ((sourceOffset < 0) || (targetOffset < 0) || (count < 0))
{
throw new System.ArgumentException();
}
// If the number of bytes from the offset to the end of the array is
// less than the number of bytes you want to copy, you cannot complete
// the copy.
if ((source.Length - sourceOffset < count) ||
(target.Length - targetOffset < count))
{
throw new System.ArgumentException();
}
// The following fixed statement pins the location of the source and
// target objects in memory so that they will not be moved by garbage
// collection.
fixed (byte* pSource = source, pTarget = target)
{
// Copy the specified number of bytes from source to target.
for (int i = 0; i < count; i++)
{
pTarget[targetOffset + i] = pSource[sourceOffset + i];
}
}
}
static void UnsafeCopyArrays()
{
// Create two arrays of the same length.
int length = 100;
byte[] byteArray1 = new byte[length];
byte[] byteArray2 = new byte[length];
// Fill byteArray1 with 0 - 99.
for (int i = 0; i < length; ++i)
{
byteArray1[i] = (byte)i;
}
// Display the first 10 elements in byteArray1.
System.Console.WriteLine("The first 10 elements of the original are:");
for (int i = 0; i < 10; ++i)
{
System.Console.Write(byteArray1[i] + " ");
}
System.Console.WriteLine("\n");
// Copy the contents of byteArray1 to byteArray2.
Copy(byteArray1, 0, byteArray2, 0, length);
// Display the first 10 elements in the copy, byteArray2.
System.Console.WriteLine("The first 10 elements of the copy are:");
for (int i = 0; i < 10; ++i)
{
System.Console.Write(byteArray2[i] + " ");
}
System.Console.WriteLine("\n");
// Copy the contents of the last 10 elements of byteArray1 to the
// beginning of byteArray2.
// The offset specifies where the copying begins in the source array.
int offset = length - 10;
Copy(byteArray1, offset, byteArray2, 0, length - offset);
// Display the first 10 elements in the copy, byteArray2.
System.Console.WriteLine("The first 10 elements of the copy are:");
for (int i = 0; i < 10; ++i)
{
System.Console.Write(byteArray2[i] + " ");
}
System.Console.WriteLine("\n");
/* Output:
The first 10 elements of the original are:
0 1 2 3 4 5 6 7 8 9
The first 10 elements of the copy are:
0 1 2 3 4 5 6 7 8 9
The first 10 elements of the copy are:
90 91 92 93 94 95 96 97 98 99
*/
}
See also
///////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////
unsafe (C# Reference)
07/20/2015 2 minutes to read
The unsafe keyword denotes an unsafe context, which is required for any operation involving pointers. For more information, see Unsafe Code and Pointers.
You can use the unsafe modifier in the declaration of a type or a member. The entire textual extent of the type or member is therefore considered an unsafe context. For example, the following is a method declared with the unsafe modifier:
C#Copy
unsafe static void FastCopy(byte[] src, byte[] dst, int count)
{
// Unsafe context: can use pointers here.
}
The scope of the unsafe context extends from the parameter list to the end of the method, so pointers can also be used in the parameter list:
C#Copy
unsafe static void FastCopy ( byte* ps, byte* pd, int count ) {...}
You can also use an unsafe block to enable the use of an unsafe code inside this block. For example:
C#Copy
unsafe
{
// Unsafe context: can use pointers here.
}
To compile unsafe code, you must specify the -unsafe compiler option. Unsafe code is not verifiable by the common language runtime.
Example
C#Copy
// compile with: -unsafe
class UnsafeTest
{
// Unsafe method: takes pointer to int.
unsafe static void SquarePtrParam(int* p)
{
*p *= *p;
}
unsafe static void Main()
{
int i = 5;
// Unsafe method: uses address-of operator (&).
SquarePtrParam(&i);
Console.WriteLine(i);
}
}
// Output: 25
C# language specification
For more information, see Unsafe code in the C# Language Specification. The language specification is the definitive source for C# syntax and usage.
See also
- C# Reference
- C# Programming Guide
- C# Keywords
- fixed Statement
- Unsafe Code and Pointers
- Fixed Size Buffers
/////////////////////////////////////////////////////////////////////////////////////////////
fixed Statement (C# Reference)
05/10/2018 3 minutes to read
The fixed statement prevents the garbage collector from relocating a movable variable. The fixed statement is only permitted in an unsafe context. You can also use the fixed keyword to create fixed size buffers.
The fixed statement sets a pointer to a managed variable and "pins" that variable during the execution of the statement. Pointers to movable managed variables are useful only in a fixed context. Without a fixed context, garbage collection could relocate the variables unpredictably. The C# compiler only lets you assign a pointer to a managed variable in a fixed statement.
C#Copy
class Point
{
public int x;
public int y;
}
unsafe private static void ModifyFixedStorage()
{
// Variable pt is a managed variable, subject to garbage collection.
Point pt = new Point();
// Using fixed allows the address of pt members to be taken,
// and "pins" pt so that it is not relocated.
fixed (int* p = &pt.x)
{
*p = 1;
}
}
You can initialize a pointer by using an array, a string, a fixed-size buffer, or the address of a variable. The following example illustrates the use of variable addresses, arrays, and strings:
C#Copy
Point point = new Point();
double[] arr = { 0, 1.5, 2.3, 3.4, 4.0, 5.9 };
string str = "Hello World";
// The following two assignments are equivalent. Each assigns the address
// of the first element in array arr to pointer p.
// You can initialize a pointer by using an array.
fixed (double* p = arr) { /*...*/ }
// You can initialize a pointer by using the address of a variable.
fixed (double* p = &arr[0]) { /*...*/ }
// The following assignment initializes p by using a string.
fixed (char* p = str) { /*...*/ }
// The following assignment is not valid, because str[0] is a char,
// which is a value, not a variable.
//fixed (char* p = &str[0]) { /*...*/ }
Starting with C# 7.3, the fixed statement operates on additional types beyond arrays, strings, fixed size buffers, or unmanaged variables. Any type that implements a method named GetPinnableReference can be pinned. The GetPinnableReference must return a ref variable of an unmanaged type. The .NET types System.Span<T> and System.ReadOnlySpan<T> introduced in .NET Core 2.0 make use of this pattern and can be pinned. This is shown in the following example:
C#Copy
unsafe private static void FixedSpanExample()
{
int[] PascalsTriangle = {
1,
1, 1,
1, 2, 1,
1, 3, 3, 1,
1, 4, 6, 4, 1,
1, 5, 10, 10, 5, 1
};
Span<int> RowFive = new Span<int>(PascalsTriangle, 10, 5);
fixed (int* ptrToRow = RowFive)
{
// Sum the numbers 1,4,6,4,1
var sum = 0;
for (int i = 0; i < RowFive.Length; i++)
{
sum += *(ptrToRow + i);
}
Console.WriteLine(sum);
}
}
If you are creating types that should participate in this pattern, see Span<T>.GetPinnableReference() for an example of implementing the pattern.
Multiple pointers can be initialized in one statement if they are all the same type:
C#Copy
fixed (byte* ps = srcarray, pd = dstarray) {...}
To initialize pointers of different types, simply nest fixed statements, as shown in the following example.
C#Copy
fixed (int* p1 = &point.x)
{
fixed (double* p2 = &arr[5])
{
// Do something with p1 and p2.
}
}
After the code in the statement is executed, any pinned variables are unpinned and subject to garbage collection. Therefore, do not point to those variables outside the fixed statement. The variables declared in the fixed statement are scoped to that statement, making this easier:
C#Copy
fixed (byte* ps = srcarray, pd = dstarray)
{
...
}
// ps and pd are no longer in scope here.
Pointers initialized in fixed statements are readonly variables. If you want to modify the pointer value, you must declare a second pointer variable, and modify that. The variable declared in the fixed statement cannot be modified:
C#Copy
fixed (byte* ps = srcarray, pd = dstarray)
{
byte* pSourceCopy = ps;
pSourceCopy++; // point to the next element.
ps++; // invalid: cannot modify ps, as it is declared in the fixed statement.
}
You can allocate memory on the stack, where it is not subject to garbage collection and therefore does not need to be pinned. To do that, use a stackalloc expression.
C# language specification
For more information, see The fixed statement section of the C# language specification.
See also
//////////////////////////////////////////////////////////////////////////////////////////
stackalloc expression (C# reference)
03/13/2020 3 minutes to read
A stackalloc expression allocates a block of memory on the stack. A stack allocated memory block created during the method execution is automatically discarded when that method returns. You cannot explicitly free the memory allocated with stackalloc. A stack allocated memory block is not subject to garbage collection and doesn't have to be pinned with a fixed statement.
You can assign the result of a stackalloc expression to a variable of one of the following types:
- Beginning with C# 7.2, System.Span<T> or System.ReadOnlySpan<T>, as the following example shows:
C#Copy
int length = 3;
Span<int> numbers = stackalloc int[length];
for (var i = 0; i < length; i++)
{
numbers[i] = i;
}
You don't have to use an unsafe context when you assign a stack allocated memory block to a Span<T> or ReadOnlySpan<T>variable.
When you work with those types, you can use a stackalloc expression in conditional or assignment expressions, as the following example shows:
C#Copy
int length = 1000;
Span<byte> buffer = length <= 1024 ? stackalloc byte[length] : new byte[length];
Beginning with C# 8.0, you can use a stackalloc expression inside other expressions whenever a Span<T> or ReadOnlySpan<T> variable is allowed, as the following example shows:
C#Copy
Span<int> numbers = stackalloc[] { 1, 2, 3, 4, 5, 6 };
var ind = numbers.IndexOfAny(stackalloc[] { 2, 4, 6 ,8 });
Console.WriteLine(ind); // output: 1
Note
We recommend using Span<T> or ReadOnlySpan<T> types to work with stack allocated memory whenever possible.
- A pointer type, as the following example shows:
C#Copy
unsafe
{
int length = 3;
int* numbers = stackalloc int[length];
for (var i = 0; i < length; i++)
{
numbers[i] = i;
}
}
As the preceding example shows, you must use an unsafe context when you work with pointer types.
In the case of pointer types, you can use a stackalloc expression only in a local variable declaration to initialize the variable.
The amount of memory available on the stack is limited. If you allocate too much memory on the stack, a StackOverflowException is thrown. To avoid that, follow the rules below:
- Limit the amount of memory you allocate with stackalloc:
C#Copy
const int MaxStackLimit = 1024;
Span<byte> buffer = inputLength <= MaxStackLimit ? stackalloc byte[inputLength] : new byte[inputLength];
Because the amount of memory available on the stack depends on the environment in which the code is executed, be conservative when you define the actual limit value.
- Avoid using stackalloc inside loops. Allocate the memory block outside a loop and reuse it inside the loop.
The content of the newly allocated memory is undefined. You should initialize it before the use. For example, you can use the Span<T>.Clear method that sets all the items to the default value of type T.
Beginning with C# 7.3, you can use array initializer syntax to define the content of the newly allocated memory. The following example demonstrates various ways to do that:
C#Copy
Span<int> first = stackalloc int[3] { 1, 2, 3 };
Span<int> second = stackalloc int[] { 1, 2, 3 };
ReadOnlySpan<int> third = stackalloc[] { 1, 2, 3 };
In expression stackalloc T[E], T must be an unmanaged type and E must evaluate to a non-negative int value.
Security
The use of stackalloc automatically enables buffer overrun detection features in the common language runtime (CLR). If a buffer overrun is detected, the process is terminated as quickly as possible to minimize the chance that malicious code is executed.
C# language specification
For more information, see the Stack allocation section of the C# language specification and the Permit stackalloc in nested contextsfeature proposal note.
See also
- C# reference
- C# operators
- Pointer related operators
- Pointer types
- Memory and span-related types
- Dos and Don'ts of stackalloc
////////////////////////////////////////////////////////////////////////////
Pointer related operators (C# reference)
05/20/2019 7 minutes to read
You can use the following operators to work with pointers:
- Unary
&
(address-of) operator: to get the address of a variable - Unary
*
(pointer indirection) operator: to obtain the variable pointed by a pointer - The
->
(member access) and[]
(element access) operators - Arithmetic operators
+
,-
,++
, and--
- Comparison operators
==
,!=
,<
,>
,<=
, and>=
For information about pointer types, see Pointer types.
Note
Any operation with pointers requires an unsafe context. The code that contains unsafe blocks must be compiled with the -unsafe
compiler option.
Address-of operator &
The unary &
operator returns the address of its operand:
C#Copy
unsafe
{
int
number =
27;
int
* pointerToNumber = &number;
Console.WriteLine(
$"Value of the variable: {number}");
Console.WriteLine(
$"Address of the variable: {(long)pointerToNumber:X}");
}
// Output is similar to:
// Value of the variable: 27
// Address of the variable: 6C1457DBD4
The operand of the &
operator must be a fixed variable. Fixed variables are variables that reside in storage locations that are unaffected by operation of the garbage collector. In the preceding example, the local variable number
is a fixed variable, because it resides on the stack. Variables that reside in storage locations that can be affected by the garbage collector (for example, relocated) are called movable variables. Object fields and array elements are examples of movable variables. You can get the address of a movable variable if you "fix", or "pin", it with a fixed
statement. The obtained address is valid only inside the block of a fixed
statement. The following example shows how to use a fixed
statement and the &
operator:
C#Copy
unsafe
{
byte
[] bytes = {
1,
2,
3};
fixed
(
byte* pointerToFirst = &bytes[
0])
{
// The address stored in pointerToFirst
// is valid only inside this fixed statement block.
}
}
You can't get the address of a constant or a value.
For more information about fixed and movable variables, see the Fixed and moveable variables section of the C# language specification.
The binary &
operator computes the logical AND of its Boolean operands or the bitwise logical AND of its integral operands.
Pointer indirection operator *
The unary pointer indirection operator *
obtains the variable to which its operand points. It's also known as the dereference operator. The operand of the *
operator must be of a pointer type.
C#Copy
unsafe
{
char
letter =
'A';
char
* pointerToLetter = &letter;
Console.WriteLine(
$"Value of the `letter` variable: {letter}");
Console.WriteLine(
$"Address of the `letter` variable: {(long)pointerToLetter:X}");
*pointerToLetter =
'Z';
Console.WriteLine(
$"Value of the `letter` variable after update: {letter}");
}
// Output is similar to:
// Value of the `letter` variable: A
// Address of the `letter` variable: DCB977DDF4
// Value of the `letter` variable after update: Z
You cannot apply the *
operator to an expression of type void*
.
The binary *
operator computes the product of its numeric operands.
Pointer member access operator ->
The ->
operator combines pointer indirection and member access. That is, if x
is a pointer of type T*
and y
is an accessible member of type T
, an expression of the form
C#Copy
x->y
is equivalent to
C#Copy
(*x).y
The following example demonstrates the usage of the ->
operator:
C#Copy
public
struct Coords
{
public
int
X;
public
int
Y;
public override string ToString()
=>
$"({X}, {Y})";
}
publicclass
PointerMemberAccessExample
{
public static unsafe void Main()
{
Coords coords;
Coords* p = &coords;
p->X =
3;
p->Y =
4;
Console.WriteLine(p->ToString());
// output: (3, 4)
}
}
You cannot apply the ->
operator to an expression of type void*
.
Pointer element access operator []
For an expression p
of a pointer type, a pointer element access of the form p[n]
is evaluated as *(p + n)
, where n
must be of a type implicitly convertible to int
, uint
, long
, or ulong
. For information about the behavior of the +
operator with pointers, see the Addition or subtraction of an integral value to or from a pointer section.
The following example demonstrates how to access array elements with a pointer and the []
operator:
C#Copy
unsafe
{
char
* pointerToChars =
stackallocchar
[
123];
for
(
inti =
65; i <
123; i++)
{
pointerToChars[i] = (
char)i;
}
Console.Write(
"Uppercase letters: ");
for
(
inti =
65; i <
91; i++)
{
Console.Write(pointerToChars[i]);
}
}
// Output:
// Uppercase letters: ABCDEFGHIJKLMNOPQRSTUVWXYZ
In the preceding example, a stackalloc
expression allocates a block of memory on the stack.
Note
The pointer element access operator doesn't check for out-of-bounds errors.
You cannot use []
for pointer element access with an expression of type void*
.
You also can use the []
operator for array element or indexer access.
Pointer arithmetic operators
You can perform the following arithmetic operations with pointers:
- Add or subtract an integral value to or from a pointer
- Subtract two pointers
- Increment or decrement a pointer
You cannot perform those operations with pointers of type void*
.
For information about supported arithmetic operations with numeric types, see Arithmetic operators.
Addition or subtraction of an integral value to or from a pointer
For a pointer p
of type T*
and an expression n
of a type implicitly convertible to int
, uint
, long
, or ulong
, addition and subtraction are defined as follows:
- Both
p + n
andn + p
expressions produce a pointer of typeT*
that results from addingn * sizeof(T)
to the address given byp
.
- The
p - n
expression produces a pointer of typeT*
that results from subtractingn * sizeof(T)
from the address given byp
.
The sizeof
operator obtains the size of a type in bytes.
The following example demonstrates the usage of the +
operator with a pointer:
C#Copy
unsafe
{
const
int
Count =
3;
int
[] numbers =
newint
[Count] {
10,
20,
30};
fixed
(
int* pointerToFirst = &numbers[
0])
{
int
* pointerToLast = pointerToFirst + (Count -
1);
Console.WriteLine(
$"Value {*pointerToFirst} at address {(long)pointerToFirst}");
Console.WriteLine(
$"Value {*pointerToLast} at address {(long)pointerToLast}");
}
}
// Output is similar to:
// Value 10 at address 1818345918136
// Value 30 at address 1818345918144
Pointer subtraction
For two pointers p1
and p2
of type T*
, the expression p1 - p2
produces the difference between the addresses given by p1
and p2
divided by sizeof(T)
. The type of the result is long
. That is, p1 - p2
is computed as ((long)(p1) - (long)(p2)) / sizeof(T)
.
The following example demonstrates the pointer subtraction:
C#Copy
unsafe
{
int
* numbers =
stackallocint
[] {
0,
1,
2,
3,
4,
5};
int
* p1 = &numbers[
1];
int
* p2 = &numbers[
5];
Console.WriteLine(p2 - p1);
// output: 4
}
Pointer increment and decrement
The ++
increment operator adds 1 to its pointer operand. The --
decrement operator subtracts 1 from its pointer operand.
Both operators are supported in two forms: postfix (p++
and p--
) and prefix (++p
and --p
). The result of p++
and p--
is the value of p
before the operation. The result of ++p
and --p
is the value of p
after the operation.
The following example demonstrates the behavior of both postfix and prefix increment operators:
C#Copy
unsafe
{
int
* numbers =
stackallocint
[] {
0,
1,
2};
int
* p1 = &numbers[
0];
int
* p2 = p1;
Console.WriteLine(
$"Before operation: p1 - {(long)p1}, p2 - {(long)p2}");
Console.WriteLine(
$"Postfix increment of p1: {(long)(p1++)}");
Console.WriteLine(
$"Prefix increment of p2: {(long)(++p2)}");
Console.WriteLine(
$"After operation: p1 - {(long)p1}, p2 - {(long)p2}");
}
// Output is similar to
// Before operation: p1 - 816489946512, p2 - 816489946512
// Postfix increment of p1: 816489946512
// Prefix increment of p2: 816489946516
// After operation: p1 - 816489946516, p2 - 816489946516
Pointer comparison operators
You can use the ==
, !=
, <
, >
, <=
, and >=
operators to compare operands of any pointer type, including void*
. Those operators compare the addresses given by the two operands as if they were unsigned integers.
For information about the behavior of those operators for operands of other types, see the Equality operators and Comparison operators articles.
Operator precedence
The following list orders pointer related operators starting from the highest precedence to the lowest:
- Postfix increment
x++
and decrementx--
operators and the->
and[]
operators
- Prefix increment
++x
and decrement--x
operators and the&
and*
operators
- Additive
+
and-
operators
- Comparison
<
,>
,<=
, and>=
operators
- Equality
==
and!=
operators
Use parentheses, ()
, to change the order of evaluation imposed by operator precedence.
For the complete list of C# operators ordered by precedence level, see the Operator precedence section of the C# operators article.
Operator overloadability
A user-defined type cannot overload the pointer related operators &
, *
, ->
, and []
.
C# language specification
For more information, see the following sections of the C# language specification:
See also
///////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////
-unsafe (C# Compiler Options)
04/25/2018 2 minutes to read
The -unsafe compiler option allows code that uses the unsafe keyword to compile.
Syntax
consoleCopy
-unsafe
Remarks
For more information about unsafe code, see Unsafe Code and Pointers.
To set this compiler option in the Visual Studio development environment
- Open the project's Properties page.
- Click the Build property page.
- Select the Allow Unsafe Code check box.
To add this option in a csproj file
Open the .csproj file for a project, and add the following elements:
XMLCopy
<PropertyGroup>
<AllowUnsafeBlocks>
true
</AllowUnsafeBlocks>
</PropertyGroup>
For information about how to set this compiler option programmatically, see AllowUnsafeBlocks.
Example
Compile in.cs
for unsafe mode:
consoleCopy
csc -unsafe in.cs
See also
/////////////////////////////////////////////////////////////////////////