Fixed and moveable variables 


Мы поможем в написании ваших работ!



ЗНАЕТЕ ЛИ ВЫ?

Fixed and moveable variables



The address-of operator (§18.5.4) and the fixed statement (§18.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.) On the other hand, moveable variables 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 (§18.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 (§18.6), and that 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.6.2) that refers to a local variable or a value parameter, unless the variable is captured by an anonymous function.

· A variable resulting from a member-access (§7.6.4) of the form V.I, where V is a fixed variable of a struct-type.

· A variable resulting from a pointer-indirection-expression (§18.5.1) of the form *P, a pointer-member-access (§18.5.2) of the form P->I, or a pointer-element-access (§18.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 conversions (§6.1) is extended to include the following implicit pointer conversions:

· From any pointer-type to the type void*.

· From the null literal to any pointer-type.

Additionally, in an unsafe context, the set of available explicit conversions (§6.2) is extended to include the following explicit pointer conversions:

· 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.

Finally, in an unsafe context, the set of standard implicit conversions (§6.3.1) includes the following pointer conversion:

· From any pointer-type to the type void*.

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:

using System;

class Test
{
unsafe static void Main() {
double d = 123.456e23;
unsafe {
byte* pb = (byte*)&d;
for (int i = 0; i < sizeof(double); ++i)
Console.Write("{0:X2} ", *pb++);
Console.WriteLine();
}
}
}

Of course, the output produced depends on endianness.

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 conversions of uint or ulong values, respectively, to or from those integral types.

Pointer arrays

In an unsafe context, arrays of pointers can be constructed. Only some of the conversions that apply to other array types are allowed on pointer arrays:

· The implicit reference conversion (§6.1.6) from any array-type to System.Array and the interfaces it implements also applies to pointer arrays. However, any attempt to access the array elements through System.Array or the interfaces it implements will result in an exception at run-time, as pointer types are not convertible to object.

· The implicit and explicit reference conversions (§6.1.6, §6.2.4) from a single-dimensional array type S[] to System.Collections.Generic.IList<T> and its generic base interfaces never apply to pointer arrays, since pointer types cannot be used as type arguments, and there are no conversions from pointer types to non-pointer types.

· The explicit reference conversion (§6.2.4) from System.Array and the interfaces it implements to any array-type applies to pointer arrays.

· The explicit reference conversions (§6.2.4) from System.Collections.Generic.IList<S> and its base interfaces to a single-dimensional array type T[] never applies to pointer arrays, since pointer types cannot be used as type arguments, and there are no conversions from pointer types to non-pointer types.

These restrictions mean that the expansion for the foreach statement over arrays described in §8.8.4 cannot be applied to pointer arrays. Instead, a foreach statement of the form

foreach (V v in x) embedded-statement

where the type of x is an array type of the form T[,,…,], n is the number of dimensions minus 1 and T or V is a pointer type, is expanded using nested for-loops as follows:

{
T[,,…,] a = x;
V v;
for (int i0 = a.GetLowerBound(0); i0 <= a.GetUpperBound(0); i0++)
for (int i1 = a.GetLowerBound(1); i1 <= a.GetUpperBound(1); i1++)

for (int in = a.GetLowerBound(n); in <= a.GetUpperBound(n); in++) {
v = (V)a.GetValue(i0,i1,…,in);
embedded-statement
}
}

The variables a, i0, i1, … in are not visible to or accessible to x or the embedded-statement or any other source code of the program. The variable v is read-only in the embedded statement. If there is not an explicit conversion (§18.4) from T (the element type) to V, an error is produced and no further steps are taken. If x has the value null, a System.NullReferenceException is thrown at run-time.

Pointers in expressions

In an unsafe context, an expression may yield a result of a pointer type, but outside an unsafe context it is a compile-time error for an expression to be of a pointer type. In precise terms, outside an unsafe context a compile-time error occurs if any simple-name (§7.6.2), member-access (§7.6.4), invocation-expression (§7.6.5), or element-access (§7.6.6) is of a pointer type.

In an unsafe context, the primary-no-array-creation-expression (§7.6) and unary-expression (§7.7) productions permit the following additional constructs:

primary-no-array-creation-expression:
...
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 a compile-time 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 §18.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 and an optional type-argument-list.

pointer-member-access:
primary-expression -> identifier type-argument-listopt

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 §18.5.1. For a description of the member access operator (.), see §7.6.4.

In the example

using System;

struct Point
{
public int x;
public int y;

public override string ToString() {
return "(" + x + "," + y + ")";
}
}

class Test
{
static void Main() {
Point point;
unsafe {
Point* p = &point;
p->x = 10;
p->y = 20;
Console.WriteLine(p->ToString());
}
}
}

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
{
static void Main() {
Point point;
unsafe {
Point* p = &point;
(*p).x = 10;
(*p).y = 20;
Console.WriteLine((*p).ToString());
}
}
}

Pointer element access

A pointer-element-access consists of a primary-no-array-creation-expression followed by an expression enclosed in “[” and “]”.

pointer-element-access:
primary-no-array-creation-expression [ 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 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 §18.5.1. For a description of the pointer addition operator (+), see §18.5.6.

In the example

class Test
{
static void Main() {
unsafe {
char* p = stackalloc char[256];
for (int i = 0; i < 256; i++) p[i] = (char)i;
}
}
}

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
{
static void Main() {
unsafe {
char* p = stackalloc char[256];
for (int i = 0; i < 256; i++) *(p + i) = (char)i;
}
}
}

The pointer element access operator does not check for out-of-bounds errors and the behavior when accessing an out-of-bounds element is undefined. This is the same as C and C++.

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 (§18.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. A compile-time error occurs if E is not classified as a variable, if E is classified as a read-only local variable, or if E denotes a moveable variable. In the last case, a fixed statement (§18.6) can be used to temporarily “fix” the variable before obtaining its address. As stated in §7.6.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.

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

using System;

class Test
{
static void Main() {
int i;
unsafe {
int* p = &i;
*p = 123;
}
Console.WriteLine(i);
}
}

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.



Поделиться:


Последнее изменение этой страницы: 2016-08-10; просмотров: 225; Нарушение авторского права страницы; Мы поможем в написании вашей работы!

infopedia.su Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав. Обратная связь - 18.222.37.169 (0.012 с.)