CATEGORII DOCUMENTE |
Unsafe code
The core C# language, as defined in the preceding chapters, differs notably from C and C++ in its omission of pointers as a data type. C# instead provides references and the ability to create objects that are managed by a garbage collector. This design, coupled with other features, makes C# a much safer language than C or C++. In the core C# language it is simply not possible to have an uninitialized variable, a "dangling" pointer, or an expression that indexes an array beyond its bounds. Whole categories of bugs that routinely plague C and C++ programs are thus eliminated.
While practically every pointer type construct in C or C++ has a reference type counterpart in C#, there are nonetheless situations where access to pointer types becomes a necessity. For example, interfacing with the underlying operating system, accessing a memory-mapped device, or implementing a time-critical algorithm may not be possible or practical without access to pointers. To address this need, C# provides the ability to write unsafe code.
In unsafe code it is possible to declare and operate on pointers, to perform conversions between pointers and integral types, to take the address of variables, and so forth. In a sense, writing unsafe code is much like writing C code within a C# program.
Unsafe code is in fact a "safe" feature from the perspective of both developers and users. Unsafe code must be clearly marked with the modifier unsafe, so developers can't possibly use unsafe features accidentally, and the execution engine works to ensure that unsafe code cannot be executed in an untrusted environment.
Unsafe contexts
The unsafe features of C# are available only in unsafe contexts. An unsafe context is introduced by including an unsafe modifier in the declaration of a type or member, or by employing an unsafe-statement:
A declaration of a class, struct, interface, or delegate may include an unsafe modifier, in which case the entire textual extent of that type declaration (including the body of the class, struct, or interface) is considered an unsafe context.
A declaration of a field, method, property, event, indexer, operator, constructor, static constructor, or destructor may include an unsafe modifier, in which case the entire textual extent of that member declaration is considered an unsafe context.
An unsafe-statement enables the use of an unsafe context within a block. The entire textual extent of the associated block is considered an unsafe context.
The associated grammar extensions are shown below. For brevity, ellipses () are used to represent productions that appear in preceding chapters.
class-modifier:
unsafe
struct-modifier:
unsafe
interface-modifier:
unsafe
delegate-modifier:
unsafe
field-modifier:
unsafe
method-modifier:
unsafe
property-modifier:
unsafe
event-modifier:
unsafe
indexer-modifier:
unsafe
operator-modifier:
unsafe
constructor-modifier:
unsafe
destructor-declaration:
attributesopt unsafeopt ~ identifier
( ) block
static-constructor-declaration:
attributesopt unsafeopt static identifier
( ) block
embedded-statement:
unsafe-statement
unsafe-statement:
unsafe block
In the example
public
unsafe struct Node
the unsafe modifier specified in the struct declaration causes the entire textual extent of the struct declaration to become an unsafe context. Thus, it is possible to declare the Left and Right fields to be of a pointer type. The example above could also be written
public
struct Node
Here, the unsafe modifiers in the field declarations cause those declarations to be considered unsafe contexts.
Other than establishing an unsafe context, thus permitting the use of pointer types, the unsafe modifier has no effect on a type or a member. In the example
public
class A
}
public
class B: A
}
the unsafe modifier on the F method in A simply causes the textual extent of F to become an unsafe context in which the unsafe features of the language can be used. In the override of F in B, there is no need to re-specify the unsafe modifier-unless, of course, the F method in B itself needs access to unsafe features.
The situation is slightly different when a pointer type is part of the method's signature
public
unsafe class A
}
public
class B: A
}
Here, because F's signature includes a pointer type, it can only be written in an unsafe context. However, the unsafe context can be introduced by either making the entire class unsafe, as is the case in A, or by including an unsafe modifier in the method declaration, as is the case in B.
Pointer types
In an unsafe context, a type (4) may be a pointer-type as well as a value-type or a reference-type.
type:
value-type
reference-type
pointer-type
A pointer-type is written as an unmanaged-type or the keyword void, followed by a * token:
pointer-type:
unmanaged-type *
void *
unmanaged-type:
type
The type specified before the * in a pointer type is called the referent type of the pointer type. It represents the type of the variable to which a value of the pointer type points.
Unlike references (values of reference types), pointers are not tracked by the garbage collector-the garbage collector has no knowledge of pointers and the data to which they point. For this reason a pointer is not permitted to point to a reference or to a structure that contains references, and the referent type of a pointer must be an unmanaged-type.
An unmanaged-type is any type that isn't a reference-type and doesn't contain reference-type fields at any level of nesting. In other words, an unmanaged-type is one of the following:
sbyte, byte, short, ushort, int, uint, long, ulong, char, float, double, decimal, or bool.
Any enum-type.
Any pointer-type.
Any user-defined struct-type that contains fields of unmanaged-types only.
The intuitive rule for mixing of pointers and references is that referents of references (objects) are permitted to contain pointers, but referents of pointers are not permitted to contain references.
Some examples of pointer types are given in the table below:
Example |
Description |
byte* |
Pointer to byte |
char* |
Pointer to char |
int** |
Pointer to pointer to int |
int*[] |
Single-dimensional array of pointers to int |
void* |
Pointer to unknown type |
For a given implementation, all pointer types must have the same size and representation.
Unlike C and C++, when multiple pointers are declared in the same declaration, in C# the * is written along with the underlying type only, not as a prefix punctuator on each pointer name. The example
int* pi, pj; // NOT as int *pi, *pj;
declares two variables, named pi and pj, of type int*.
The value of a pointer having type T* represents the address of a variable of type T. The pointer indirection operator * (A.5.1) may be used to access this variable. For example, given a variable P of type int*, the expression *P denotes the int variable found at the address contained in P.
Like an object reference, a pointer may be null. Applying the indirection operator to a null pointer results in implementation-defined behavior. A pointer with the value null is represented by all-bits-zero.
The void* type represents a pointer to an unknown type. Because the referent type is unknown, the indirection operator cannot be applied to a pointer of type void*, nor can any arithmetic be performed on such a pointer. However, a pointer of type void* can be cast to any other pointer type (and vice versa).
Pointer types are a separate category of types. Unlike reference types and value types, pointer types do not inherit from object and no conversions exist between pointer types and object. In particular, boxing and unboxing (4.3) are not supported for pointers. However, conversions are permitted between different pointer types and between pointer types and the integral types. This is described in A.4.
Although pointers can be passed as ref or out parameters, doing so can cause undefined behavior, since the pointer may well be set to point to a local variable which no longer exists when the called method returns, or the fixed object to which it used to point, is no longer fixed. For example:
class
Test
}
unsafe static void
*px1, *px2); // undefined behavior
}
}
A method can return a value of some type, and that type can be a pointer. For example, when given a pointer to a contiguous sequence of int values, the sequence's element count, and some other int value, the following method returns the address of the indicated value in that array, if a match occurs; otherwise it returns null:
unsafe
static int* Find(int* pi, int size, int value)
return null;
}
In an unsafe context, several constructs are available for operating on pointers:
The * operator may be used to perform pointer indirection (A.5.1).
The -> operator may be used to access a member of a struct through a pointer (A.5.2).
The [] operator may be used to index a pointer (A.5.3).
The & operator may be used to obtain the address of a variable (A.5.4).
The ++ and -- operators may be used to increment and decrement pointers (A.5.5).
The + and - operators may be used to perform pointer arithmetic (A.5.6).
The ==, !=, <, >, <=, and => operators may be used to compare pointers (A.5.7).
The stackalloc operator may be used to allocate memory from the call stack (A.7).
The fixed statement may be used to temporarily fix a variable so its address can be obtained (A.6).
Fixed and moveable variables
The address-of operator (A.5.4) and the fixed statement (A.6) divide variables into two categories: Fixed variables and moveable variables.
Fixed variables reside in storage locations that are unaffected by operation of the garbage collector. Examples of fixed variables include local variables, value parameters, and variables created by dereferencing pointers. Moveable variables on the other hand reside in storage locations that are subject to relocation or disposal by the garbage collector. Examples of moveable variables include fields in objects and elements of arrays.
The & operator (A.5.4) permits the address of a fixed variable to be obtained without restrictions. However, because a moveable variable is subject to relocation or disposal by the garbage collector, the address of a moveable variable can only be obtained using a fixed statement (A.6), and the address remains valid only for the duration of that fixed statement.
In precise terms, a fixed variable is one of the following:
A variable resulting from a simple-name (7.5.2) that refers to a local variable or a value parameter.
A variable resulting from a member-access (7.5.4) of the form V.I, where V is a fixed variable of a struct-type.
A variable resulting from a pointer-indirection-expression (A.5.1) of the form *P, a pointer-member-access (A.5.2) of the form P->I, or a pointer-element-access (A.5.3) of the form P[E].
All other variables are classified as moveable variables.
Note that a static field is classified as a moveable variable. Also note that a ref or out parameter is classified as a moveable variable, even if the argument given for the parameter is a fixed variable. Finally, note that a variable produced by dereferencing a pointer is always classified as a fixed variable.
Pointer conversions
In an unsafe context, the set of available implicit and explicit conversions is extended to include pointer types as described in this section.
Implicit pointer conversions can occur in a variety of situations within unsafe contexts, including function member invocations (7.4.3), cast expressions (7.6.8), and assignments (7.13). The implicit pointer conversions are:
From any pointer-type to the type void*.
From the null type to any pointer-type.
Explicit pointer conversions can occur only in cast expressions (7.6.8) within unsafe contexts. The explicit pointer conversions are:
From any pointer-type to any other pointer-type.
From sbyte, byte, short, ushort, int, uint, long, or ulong to any pointer-type.
From any pointer-type to sbyte, byte, short, ushort, int, uint, long, or ulong.
For further details on implicit and explicit conversions, see 6.1 and 6.2.
Conversions between two pointer types never change the actual pointer value. In other words, a conversion from one pointer type to another has no effect on the underlying address given by the pointer.
When one pointer type is converted to another, if the resulting pointer is not correctly aligned for the pointed-to type, the behavior is undefined if the result is dereferenced. In general, the concept "correctly aligned" is transitive: if a pointer to type A is correctly aligned for a pointer to type B, which, in turn, is correctly aligned for a pointer to type C, then a pointer to type A is correctly aligned for a pointer to type C.
Consider the following case in which a variable having one type is accessed via a pointer to a different type:
char c =
'A';
char* pc = &c;
void* pv = pc;
int* pi = (int*)pv;
int i = *pi; // undefined
*pi = 123456; // undefined
When a pointer type is converted to a pointer to byte, the result points to the lowest addressed byte of the variable. Successive increments of the result, up to the size of the variable, yield pointers to the remaining bytes of that variable. For example, the following method displays each of the eight bytes in a double as a hexadecimal value:
class
Test
', (uint)(*pb++));
}
}
Of course, the output produced depends on the endianness of a double.
Mappings between pointers and integers are implementation-defined. However, on 32- and 64-bit CPU architectures with a linear address space, conversions of pointers to or from integral types typically behave exactly like a conversion of uint or ulong values, respectively, to or from those integral types.
Pointers in expressions
In an unsafe context an expression may yield a result of a pointer type, but outside an unsafe context it is an error for an expression to be of a pointer type. In precise terms, outside an unsafe context an error occurs if any simple-name (7.5.2), member-access (7.5.4), invocation-expression (7.5.5), or element-access (7.5.6) is of a pointer type.
In an unsafe context, the primary-expression-no-array-creation (7.5) and unary-expression (7.6) productions permit the following additional constructs:
primary-expression-no-array-creation:
pointer-member-access
pointer-element-access
sizeof-expression
unary-expression:
pointer-indirection-expression
addressof-expression
These constructs are described in the following sections. The precedence and associativity of the unsafe operators is implied by the grammar.
Pointer indirection
A pointer-indirection-expression consists of an asterisk (*) followed by a unary-expression.
pointer-indirection-expression:
*
unary-expression
The unary * operator denotes pointer indirection and is used to obtain the variable to which a pointer points. The result of evaluating *P, where P is an expression of a pointer type T*, is a variable of type T. It is an error to apply the unary * operator to an expression of type void* or to an expression that isn't of a pointer type.
The effect of applying the unary * operator to a null pointer is implementation-defined. In particular, there is no guarantee that this operation throws a System.NullReferenceException.
If an invalid value has been assigned to the pointer, the behavior of the unary * operator is undefined. Among the invalid values for dereferencing a pointer by the unary * operator are an address inappropriately aligned for the type pointed to (see example in A.4), and the address of a variable after the end of its lifetime.
For purposes of definite assignment analysis, a variable produced by evaluating an expression of the form *P is considered initially assigned (5.3.1).
Pointer member access
A pointer-member-access consists of a primary-expression, followed by a "->" token, followed by an identifier.
pointer-member-access:
primary-expression -> identifier
In a pointer member access of the form P->I, P must be an expression of a pointer type other than void*, and I must denote an accessible member of the type to which P points.
A pointer member access of the form P->I is evaluated exactly as (*P).I. For a description of the pointer indirection operator (*), see A.5.1. For a description of the member access operator (.), see 7.5.4.
In the example
struct Point
}
class Test
}
the -> operator is used to access fields and invoke a method of a struct through a pointer. Because the operation P->I is precisely equivalent to (*P).I, the Main method could equally well have been written:
class Test
}
Pointer element access
A pointer-element-access consists of a primary-expression followed by an expression enclosed in "[" and "]".
pointer-element-access:
primary-expression-no-array-creation [ expression
]
In a pointer element access of the form P[E], P must be an expression of a pointer type other than void*, and E must be an expression of a type that can be implicitly converted to int, uint, long, or ulong.
A pointer element access of the form P[E] is evaluated exactly as *(P + E). For a description of the pointer indirection operator (*), see A.5.1. For a description of the pointer addition operator (+), see A.5.6.
In the example
class Test
}
a pointer element access is used to initialize the character buffer in a for loop. Because the operation P[E] is precisely equivalent to *(P + E), the example could equally well have been written:
class Test
}
The pointer element access operator does not check for out-of-bounds errors and the effects of accessing an out-of-bounds element are undefined.
The address-of operator
An addressof-expression consists of an ampersand (&) followed by a unary-expression.
addressof-expression:
& unary-expression
Given an expression E which is of a type T and is classified as a fixed variable (A.3), the construct &E computes the address of the variable given by E. The type of the result is T* and is classified as a value. An error occurs if E is not classified as a variable, if E is classified as a volatile field, or if E denotes a moveable variable. In the last case, a fixed statement (A.6) can be used to temporarily "fix" the variable before obtaining its address.
The & operator does not require its argument to be definitely assigned, but following an & operation, the variable to which the operator is applied is considered definitely assigned in the execution path in which the operation occurs. It is the responsibility of the programmer to ensure that correct initialization of the variable actually does take place in this situation.
In the example
unsafe
class Test
}
i is considered definitely assigned following the &i operation used to initialize p. The assignment to *p in effect initializes i, but the inclusion of this initialization is the responsibility of the programmer, and no compile-time error would occur if the assignment was removed.
The rules of definite assignment for the & operator exist such that redundant initialization of local variables can be avoided. For example, many external APIs take a pointer to a structure which is filled in by the API. Calls to such APIs typically pass the address of a local struct variable, and without the rule, redundant initialization of the struct variable would be required.
As stated earlier (7.5.4), outside an instance constructor or static constructor for a struct or class that defines a readonly field, that field is considered a value, not a variable. As such, its address cannot be taken. Similarly, the address of a constant cannot be taken.
Pointer increment and decrement
In an unsafe context, the ++ and ‑‑ operators (7.5.9 and 7.6.7) can be applied to pointer variables of all types except void*. Thus, for every pointer type T*, the following operators are implicitly defined:
T* operator ++(T* x);
T* operator --(T* x);
The operators produce the same results asx+1 and x-1, respectively (A.5.6). In other words, for a pointer variable of type T*, the ++ operator adds sizeof(T) to the address contained in the variable, and the ‑‑ operator subtracts sizeof(T) from the address contained in the variable.
If a pointer increment or decrement operation overflows the domain of the pointer type, the result is truncated in an implementation-defined fashion, but no exceptions are produced.
Pointer arithmetic
In an unsafe context, the + and - operators (7.7.4 and 7.7.5) can be applied to values of all pointer types except void*. Thus, for every pointer type T*, the following operators are implicitly defined:
T*
operator +(T* x, int y);
T* operator +(T* x, uint y);
T* operator +(T* x, long y);
T* operator +(T* x, ulong y);
T*
operator +(int x, T* y);
T* operator +(uint x, T* y);
T* operator +(long x, T* y);
T* operator +(ulong x, T* y);
T*
operator -(T* x, int y);
T* operator -(T* x, uint y);
T* operator -(T* x, long y);
T* operator -(T* x, ulong y);
long operator -(T* x, T* y);
Given an expression P of a pointer type T* and an expression N of type int, uint, long, or ulong, the expressions P + N and N + P compute the pointer value of type T* that results from adding N * sizeof(T) to the address given by P. Likewise, the expression P - N computes the pointer value of type T* that results from subtracting N * sizeof(T) from the address given by P.
Given two expressions, P and Q, of a pointer type T*, the expression P - Q computes the difference between the addresses given by P and Q and then divides the difference by sizeof(T). The type of the result is always long. In effect, P - Q is computed as ((long)(P) - (long)(Q)) / sizeof(T).
For example, this program:
class Test
', p - q);
Console.WriteLine('q - p =
', q - p);
}
}
produces the output:
p - q =
-14
q - p = 14
If a pointer arithmetic operation overflows the domain of the pointer type, the result is truncated in an implementation-defined fashion, but no exceptions are produced.
Pointer comparison
In an unsafe context, the ==, !=, <, >, <=, and => operators (7.9) can be applied to values of all pointer types. The pointer comparison operators are:
bool operator ==(void* x, void* y);
bool operator !=(void* x, void* y);
bool operator <(void* x, void* y);
bool operator >(void* x, void* y);
bool operator <=(void* x, void* y);
bool operator >=(void* x, void* y);
Because an implicit conversion exists from any pointer type to the void* type, operands of any pointer type can be compared using these operators. The comparison operators compare the addresses given by the two operands as if they were unsigned integers.
The sizeof operator
The sizeof operator returns the number of bytes occupied by a variable of a given type. The type specified as an operand to sizeof must be an unmanaged-type (A.2).
sizeof-expression:
sizeof ( unmanaged-type )
The result of the sizeof operator is a value of type int. For certain predefined types, the sizeof operator yields a constant value as shown in the table below.
Expression |
Result |
sizeof(sbyte) |
1 |
sizeof(byte) |
1 |
sizeof(short) |
2 |
sizeof(ushort) |
2 |
sizeof(int) |
4 |
sizeof(uint) |
4 |
sizeof(long) |
8 |
sizeof(ulong) |
8 |
sizeof(char) |
2 |
sizeof(float) |
4 |
sizeof(double) |
8 |
sizeof(bool) |
1 |
For all other types, the result of the sizeof operator is implementation-defined and is classified as a value, not a constant.
The order in which non-function members are packed into a struct is undefined.
For alignment purposes, there may be unnamed padding at the beginning of a struct, within a struct, and at the end of the struct. The contents of the bits used as padding are indeterminate.
When applied to an operand that has struct type, the result is the total number of bytes in a variable of that type, including any padding.
The fixed statement
In an unsafe context, the embedded-statement (8) production permits an additional construct, the fixed statement, which is used to "fix" a moveable variable such that its address remains constant for the duration of the statement.
embedded-statement:
fixed-statement
fixed-statement:
fixed ( pointer-type fixed-pointer-declarators ) embedded-statement
fixed-pointer-declarators:
fixed-pointer-declarator
fixed-pointer-declarators , fixed-pointer-declarator
fixed-pointer-declarator:
identifier = fixed-pointer-initializer
fixed-pointer-initializer:
& variable-reference
expression
Each fixed-pointer-declarator declares a local variable of the given pointer-type and initializes the local variable with the address computed by the corresponding fixed-pointer-initializer. A local variable declared in a fixed statement is accessible in any fixed-pointer-initializers occurring to the right of the declaration, and in the embedded-statement of the fixed statement. A local variable declared by a fixed statement is considered read-only and cannot be assigned to or passed as a ref or out parameter.
A fixed-pointer-initializer can be one of the following:
The token "&" followed by a variable-reference (5.4) to a moveable variable (A.3) of an unmanaged type T, provided the type T* is implicitly convertible to the pointer type given in the fixed statement. In this case, the initializer computes the address of the given variable, and the variable is guaranteed to remain at a fixed address for the duration of the fixed statement.
An expression of an array-type with elements of an unmanaged type T, provided the type T* is implicitly convertible to the pointer type given in the fixed statement. In this case, the initializer computes the address of the first element in the array, and the entire array is guaranteed to remain at a fixed address for the duration of the fixed statement. A System.NullReferenceException is thrown if the array expression is null; a System.IndexOutOfRangeException is thrown if the array has no elements.
An expression of type string, provided the type char* is implicitly convertible to the pointer type given in the fixed statement. In this case, the initializer computes the address of the first character in the string, and the entire string is guaranteed to remain at a fixed address for the duration of the fixed statement. A System.NullReferenceException is thrown if the string expression is null.
For each address computed by a fixed-pointer-initializer the fixed statement ensures that the variable referenced by the address is not subject to relocation or disposal by the garbage collector for the duration of the fixed statement. For example, if the address computed by a fixed-pointer-initializer references a field of an object or an element of an array instance, the fixed statement guarantees that the containing object instance is not relocated or disposed of during the lifetime of the statement.
It is the programmer's responsibility to ensure that pointers created by fixed statements do not survive beyond execution of those statements. This for example means that when pointers created by fixed statements are passed to external APIs, it is the programmer's responsibility to ensure that the APIs retain no memory of these pointers.
The fixed statement is typically implemented by generating tables that describe to the garbage collector which objects are to remain fixed in which regions of executable code. Thus, as long as a garbage collection process doesn't actually occur during execution of a fixed statement, there is very little cost associated with the statement. However, when a garbage collection process does occur, fixed objects may cause fragmentation of the heap (because they can't be moved). For that reason, objects should be fixed only when absolutely necessary and then only for the shortest amount of time possible.
The example
unsafe
class Test
static void Main()
}
demonstrates several uses of the fixed statement. The first statement fixes and obtains the address of a static field, the second statement fixes and obtains the address of an instance field, and the third statement fixes and obtains the address of an array element. In each case it would have been an error to use the regular & operator since the variables are all classified as moveable variables.
The third and fourth fixed statements in the example above produce identical results. In general, for an array instance arr, specifying &arr[0] in a fixed statement is the same as simply specifying arr.
Here's another example of the fixed statement, this time using string:
class
Test
unsafe static void
}
Within a fixed statement that obtains a pointer p to an array instance a, the pointer values ranging from p to p + arr.Length - 1 represent addresses of the elements in the array. Likewise, the variables ranging from p[0] to p[arr.Length - 1] represent the actual array elements. Given the way in which arrays are stored (7.5.10.2), we can treat an array of any dimension as though it were linear.
The example
using
System;
class Test
}
for (int i = 0; i < 2; ++i)
for (int j = 0; j < 3;
++j) {
for (int k = 0; k
< 4; ++k)
Console.Write('[,,]
= ', i, j, k, a[i,j,k]);
Console.WriteLine();
}
}
}
produces the output:
[0,0,0]
= 0 [0,0,1] = 1 [0,0,2] =
2 [0,0,3] = 3
[0,1,0] = 4 [0,1,1] = 5 [0,1,2] =
6 [0,1,3] = 7
[0,2,0] = 8 [0,2,1] = 9 [0,2,2] = 10 [0,2,3] = 11
[1,0,0] = 12 [1,0,1] = 13 [1,0,2] = 14 [1,0,3] = 15
[1,1,0] = 16 [1,1,1] = 17 [1,1,2] = 18 [1,1,3] = 19
[1,2,0] = 20 [1,2,1] = 21 [1,2,2] = 22 [1,2,3] = 23
In the example
unsafe
class Test
static void
}
a fixed statement is used to fix an array so its address can be passed to a method that takes a pointer.
A char* value produced by fixing a string instance always points to a null-terminated string. Within a fixed statement that obtains a pointer p to a string instance s, the pointer values ranging from p to p + s.Length - 1 represent addresses of the characters in the string, and the pointer value p + s.Length always points to a null character (the character with value '0').
Because strings are immutable, it is the programmer's responsibility to ensure that the characters referenced by a pointer to a fixed string are not modified.
The automatic null-termination of strings is particularly convenient when calling external APIs that expect "C-style" strings. Note, however, that a string instance is permitted to contain null characters. If such null characters are present, the string will appear truncated when treated as a null-terminated char*.
Stack allocation
In an unsafe context, a local variable declaration (8.5.1) may include a stack allocation initializer which allocates memory from the call stack.
variable-initializer:
expression
array-initializer
stackalloc-initializer
stackalloc-initializer:
stackalloc unmanaged-type [ expression
]
The unmanaged-type indicates the type of the items that will be stored in the newly allocated location, and the expression indicates the number of these items. Taken together, these specify the required allocation size. Since the size of a stack allocation cannot be negative, it is an error to specify the number of items as a constant-expression that evaluates to a negative value. The size of a stack allocation may be zero, and in such a case no allocation is made at all.
A stack allocation initializer of the form stackalloc T[E] requires T to be an unmanaged type (A.2) and E to be an expression of type int. The construct allocates E * sizeof(T) bytes from the call stack and produces a pointer, of type T*, to the newly allocated block. If E is a negative value, then the behavior is undefined. If there is not enough memory available to allocate a block of the given size, a System.StackOverflowException is thrown.
The content of the newly allocated memory is undefined.
There is no way to explicitly free memory allocated using stackalloc. Instead, all stack allocated memory blocks created during the execution of a function member are automatically discarded when the function member returns. This corresponds to the alloca function, an extension commonly found in C and C++ implementations.
Stack allocation initializers are not permitted in catch or finally blocks (8.10).
In the example
class
Test
while (n != 0);
if (value < 0) *--p = '-';
return new string(p,
(int)(buffer + 16 - p));
}
static void
}
a stackalloc initializer is used in the IntToString method to allocate a buffer of 16 characters on the stack. The buffer is automatically discarded when the method returns.
Dynamic memory allocation
Except for the stackalloc operator, C# provides no predefined constructs for managing non-garbage collected memory. Such services are typically provided by supporting class libraries or imported directly from the underlying operating system. For example, the Memory class below illustrates how the Heap Functions of the Windows API can be accessed from C#:
using
System;
using System.Runtime.InteropServices;
public
unsafe class Memory
// Allocates a memory block of the given
size. The allocated memory is
// automatically initialized to zero.
public static void* Alloc(int size)
// Copies count bytes from src to dst. The
source and destination
// blocks are permitted to overlap.
public static void Copy(void* src, void*
dst, int count)
else if (ps < pd)
}
// Frees a memory block.
public static void Free(void* block)
// Re-allocates a memory block. If the
reallocation request is for a
// larger size, the additional region
of memory is automatically
// initialized to zero.
public static void* ReAlloc(void* block, int size)
// Returns the size of a memory block.
public static int SizeOf(void* block)
// Heap API flags
const int HEAP_ZERO_MEMORY = 0x00000008;
// Heap API functions
[DllImport('kernel32')]
static extern int GetProcessHeap();
[DllImport('kernel32')]
static extern void* HeapAlloc(int
hHeap, int flags, int size);
[DllImport('kernel32')]
static extern bool HeapFree(int
hHeap, int flags, void* block);
[DllImport('kernel32')]
static extern void* HeapReAlloc(int
hHeap, int flags,
void* block, int size);
[DllImport('kernel32')]
static extern int HeapSize(int hHeap,
int flags, void* block);
}
An example that uses the Memory class is given below:
class
Test
}
The example allocates 256 bytes of memory through Memory.Alloc and initializes the memory block with values increasing from 0 to 255. It then allocates a 256 element byte array and uses Memory.Copy to copy the contents of the memory block into the byte array. Finally, the memory block is freed using Memory.Free and the contents of the byte array are output on the console.
Politica de confidentialitate | Termeni si conditii de utilizare |
Vizualizari: 990
Importanta:
Termeni si conditii de utilizare | Contact
© SCRIGROUP 2024 . All rights reserved