不安全代码Unsafe code

根据上述章节中定义的核心 c # 语言,在将指针省略为数据类型时,它的不同之处在于 C 和 c + +。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, C# provides references and the ability to create objects that are managed by a garbage collector. 此设计与其他功能结合使用,使 c # 比 C 或 c + + 更安全。This design, coupled with other features, makes C# a much safer language than C or C++. 在核心 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. 这样就消除了经常灾难 C 和 c + + 程序的各种 bug。Whole categories of bugs that routinely plague C and C++ programs are thus eliminated.

尽管 C 或 c + + 中的每个指针类型构造实际上都具有与 c # 对应的引用类型,但在某些情况下,访问指针类型是必需的。While practically every pointer type construct in C or C++ has a reference type counterpart in C#, nonetheless, there are 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. 为了满足这一需要,c # 提供了编写 不安全代码 的功能。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. 在某种意义上,编写不安全代码非常类似于在 c # 程序中编写 C 代码。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 ,因此开发人员不可能意外地使用不安全的功能,并且执行引擎可以确保不受信任的环境中不能执行不安全的代码。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

C # 的 unsafe 功能只能在不安全的上下文中使用。The unsafe features of C# are available only in unsafe contexts. 通过 unsafe 在类型或成员的声明中包含修饰符或使用 unsafe_statement,引入了不安全的上下文:An unsafe context is introduced by including an unsafe modifier in the declaration of a type or member, or by employing an unsafe_statement:

  • 类、结构、接口或委托的声明可能包含 unsafe 修饰符,在这种情况下,该类型声明的整个文本区 (包括类、结构或接口) 的主体被视为不安全的上下文。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.
  • 字段、方法、属性、事件、索引器、运算符、实例构造函数、析构函数或静态构造函数的声明可能包含 unsafe 修饰符,在这种情况下,该成员声明的整个文本范围被视为不安全的上下文。A declaration of a field, method, property, event, indexer, operator, instance constructor, destructor, or static constructor may include an unsafe modifier, in which case the entire textual extent of that member declaration is considered an unsafe context.
  • Unsafe_statement 允许在 内使用不安全的上下文。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 productions are shown below.

class_modifier_unsafe
    : 'unsafe'
    ;

struct_modifier_unsafe
    : 'unsafe'
    ;

interface_modifier_unsafe
    : 'unsafe'
    ;

delegate_modifier_unsafe
    : 'unsafe'
    ;

field_modifier_unsafe
    : 'unsafe'
    ;

method_modifier_unsafe
    : 'unsafe'
    ;

property_modifier_unsafe
    : 'unsafe'
    ;

event_modifier_unsafe
    : 'unsafe'
    ;

indexer_modifier_unsafe
    : 'unsafe'
    ;

operator_modifier_unsafe
    : 'unsafe'
    ;

constructor_modifier_unsafe
    : 'unsafe'
    ;

destructor_declaration_unsafe
    : attributes? 'extern'? 'unsafe'? '~' identifier '(' ')' destructor_body
    | attributes? 'unsafe'? 'extern'? '~' identifier '(' ')' destructor_body
    ;

static_constructor_modifiers_unsafe
    : 'extern'? 'unsafe'? 'static'
    | 'unsafe'? 'extern'? 'static'
    | 'extern'? 'static' 'unsafe'?
    | 'unsafe'? 'static' 'extern'?
    | 'static' 'extern'? 'unsafe'?
    | 'static' 'unsafe'? 'extern'?
    ;

embedded_statement_unsafe
    : unsafe_statement
    | fixed_statement
    ;

unsafe_statement
    : 'unsafe' block
    ;

示例中In the example

public unsafe struct Node
{
    public int Value;
    public Node* Left;
    public Node* Right;
}

unsafe结构声明中指定的修饰符导致结构声明的整个文本区成为不安全的上下文。the unsafe modifier specified in the struct declaration causes the entire textual extent of the struct declaration to become an unsafe context. 因此,可以将 LeftRight 字段声明为指针类型。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
{
    public int Value;
    public unsafe Node* Left;
    public unsafe Node* Right;
}

此处, unsafe 字段声明中的修饰符将导致这些声明被视为不安全的上下文。Here, the unsafe modifiers in the field declarations cause those declarations to be considered unsafe contexts.

除了建立不安全的上下文,因而允许使用指针类型外,修饰符对 unsafe 类型或成员不起作用。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 unsafe virtual void F() {
        char* p;
        ...
    }
}

public class B: A
{
    public override void F() {
        base.F();
        ...
    }
}

unsafe中的方法的修饰符 F A 只是导致的文本范围 F 成为不安全的上下文,在此上下文中,可以使用该语言的不安全功能。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. 在的重写 FB ,无需重新指定 unsafe 修饰符--除非当然, F 方法 B 本身需要访问不安全功能。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 virtual void F(char* p) {...}
}

public class B: A
{
    public unsafe override void F(char* p) {...}
}

此处,由于 F 的签名包含指针类型,因此只能在不安全的上下文中写入。Here, because F's signature includes a pointer type, it can only be written in an unsafe context. 但是,通过使整个类不安全(如中的情况) A 或在 unsafe 方法声明中包含修饰符(如中的情况),可以引入 unsafe 上下文 BHowever, 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

在不安全的上下文中, 类型 (类型) 可以是 pointer_typevalue_typereference_typeIn an unsafe context, a type (Types) may be a pointer_type as well as a value_type or a reference_type. 但是,还可以在表达式中使用 pointer_type (在不安全 typeof 的上下文外) 匿名对象创建表达式 ,因为这种用法是不安全的。However, a pointer_type may also be used in a typeof expression (Anonymous object creation expressions) outside of an unsafe context as such usage is not unsafe.

type_unsafe
    : pointer_type
    ;

Pointer_typeunmanaged_type 或关键字形式编写 void ,后跟一个 * 令牌: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. 出于此原因,不允许指针指向引用或包含引用的结构,并且指针的引用类型必须是 unmanaged_typeFor this reason a pointer is not permitted to point to a reference or to a struct that contains references, and the referent type of a pointer must be an unmanaged_type.

Unmanaged_typereference_type 或构造类型之外的任何类型,并且在任何嵌套级别都不包含 reference_type 或构造类型字段。An unmanaged_type is any type that isn't a reference_type or constructed type, and doesn't contain reference_type or constructed type fields at any level of nesting. 换句话说, unmanaged_type 是以下项之一:In other words, an unmanaged_type is one of the following:

  • sbytebyteshortushortintuintlongulong char float double decimal 、、、、或 boolsbyte, byte, short, ushort, int, uint, long, ulong, char, float, double, decimal, or bool.
  • 任何 enum_typeAny enum_type.
  • 任何 pointer_typeAny pointer_type.
  • 任何不是构造类型并且只包含 unmanaged_type 的字段的用户定义的 struct_typeAny user-defined struct_type that is not a constructed type and contains fields of unmanaged_type s 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* 指向 bytePointer to byte
char* 指向 charPointer to char
int** 指向指向的指针的指针 intPointer to pointer to int
int*[] 指向的指针的一维数组 intSingle-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.

与 C 和 c + + 不同,在 c # 中声明多个指针时, * 仅与基础类型一起编写,而不是作为每个指针名称的前缀标点符号。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. 例如For example

int* pi, pj;    // NOT as int *pi, *pj;

具有 type 的指针的值 T* 表示类型的变量的地址 TThe value of a pointer having type T* represents the address of a variable of type T. 指针间接寻址运算符 * (指针间接寻址) 可用于访问此变量。The pointer indirection operator * (Pointer indirection) may be used to access this variable. 例如,给定一个 P 类型为的变量 int* ,该表达式 *P 表示在 int 中包含的地址处找到的变量 PFor example, given a variable P of type int*, the expression *P denotes the int variable found at the address contained in P.

与对象引用类似,指针可以为 nullLike an object reference, a pointer may be null. 将间接寻址运算符应用于 null 指针将导致实现定义的行为。Applying the indirection operator to a null pointer results in implementation-defined behavior. 具有值的指针用 null 全部位0表示。A pointer with value null is represented by all-bits-zero.

void*类型表示指向未知类型的指针。The void* type represents a pointer to an unknown type. 由于引用类型未知,因此间接寻址运算符不能应用于类型的指针 void* ,也不能对此类指针执行任何算术运算。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. 但类型的指针 void* 可以转换为任何其他指针类型 (,反之亦然) 。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. 与引用类型和值类型不同,指针类型不从继承, object 并且指针类型与之间不存在转换 objectUnlike 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 (Boxing and unboxing) 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 Pointer conversions.

Pointer_type 不能用作类型参数 (构造类型) ,并且类型推理 (类型推理在泛型方法调用中) 失败,这会将类型参数推断为指针类型。A pointer_type cannot be used as a type argument (Constructed types), and type inference (Type inference) fails on generic method calls that would have inferred a type argument to be a pointer type.

Pointer_type 可以用作可变 字段 () 可变字段的类型。A pointer_type may be used as the type of a volatile field (Volatile fields).

尽管指针可以作为 ref 或参数传递 out ,但这样做可能会导致未定义的行为,因为在调用的方法返回时,指针可能设置为指向某个局部变量,而在被调用的方法返回时,或其所指向的固定对象不再是固定的。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:

using System;

class Test
{
    static int value = 20;

    unsafe static void F(out int* pi1, ref int* pi2) {
        int i = 10;
        pi1 = &i;

        fixed (int* pj = &value) {
            // ...
            pi2 = pj;
        }
    }

    static void Main() {
        int i = 10;
        unsafe {
            int* px1;
            int* px2 = &i;

            F(out px1, ref px2);

            Console.WriteLine("*px1 = {0}, *px2 = {1}",
                *px1, *px2);    // undefined behavior
        }
    }
}

方法可以返回某种类型的值,并且该类型可以是指针。A method can return a value of some type, and that type can be a pointer. 例如,当给定一个指向连续序列的指针 int 、序列的元素计数和其他某个 int 值时,如果发生匹配,以下方法将返回该序列中的该值的地址; 否则返回 nullFor example, when given a pointer to a contiguous sequence of ints, that sequence's element count, and some other int value, the following method returns the address of that value in that sequence, if a match occurs; otherwise it returns null:

unsafe static int* Find(int* pi, int size, int value) {
    for (int i = 0; i < size; ++i) {
        if (*pi == value) 
            return pi;
        ++pi;
    }
    return null;
}

在不安全的上下文中,有多个构造可用于对指针进行操作:In an unsafe context, several constructs are available for operating on pointers:

固定和可移动变量Fixed and moveable variables

Address 运算符 (地址运算符) , fixed (fixed) 语句 中的语句将变量划分为两个类别: *固定变量 _ 和 _ 可移动变量 *。The address-of operator (The address-of operator) and the fixed statement (The fixed statement) 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 (The address-of operator) permits the address of a fixed variable to be obtained without restrictions. 但是,由于可移动变量可能会由垃圾回收器重定位或处置,因此只能使用 fixed fixed 语句) (语句获取可移动变量的地址,并且该地址在该语句的持续时间内保持有效 fixedHowever, 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 (The fixed statement), and that address remains valid only for the duration of that fixed statement.

确切地说,固定变量是以下项之一:In precise terms, a fixed variable is one of the following:

  • Simple_name (简单名称) 引用本地变量或值参数而产生的变量,除非该变量由匿名函数捕获。A variable resulting from a simple_name (Simple names) that refers to a local variable or a value parameter, unless the variable is captured by an anonymous function.
  • member_access (成员访问) 生成的变量 V.I ,其中 Vstruct_type 的固定变量。A variable resulting from a member_access (Member access) of the form V.I, where V is a fixed variable of a struct_type.
  • 从窗体的 pointer_indirection_expression (指针间接寻址 、) 窗体 *Ppointer_member_access (指针成员访问) P->I 或窗体 pointer_element_access ( 指针元素访问) 生成的变量 P[E]A variable resulting from a pointer_indirection_expression (Pointer indirection) of the form *P, a pointer_member_access (Pointer member access) of the form P->I, or a pointer_element_access (Pointer element access) of the form P[E].

所有其他变量归类为可移动变量。All other variables are classified as moveable variables.

请注意,静态字段归类为可移动变量。Note that a static field is classified as a moveable variable. 另请注意, refout 参数归类为可移动变量,即使为参数提供的参数是固定变量。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 (Implicit conversions) is extended to include the following implicit pointer conversions:

  • 从任何 pointer_type 到类型 void*From any pointer_type to the type void*.
  • null 文本到任何 pointer_typeFrom the null literal to any pointer_type.

此外,在不安全的上下文中, (显式转换 的可用显式转换集) 扩展为包含以下显式指针转换:Additionally, in an unsafe context, the set of available explicit conversions (Explicit conversions) is extended to include the following explicit pointer conversions:

  • 从任何 pointer_type 到任何其他 pointer_typeFrom any pointer_type to any other pointer_type.
  • sbytebyte 、、、、、 short ushort int uint longulong 到任何 pointer_typeFrom sbyte, byte, short, ushort, int, uint, long, or ulong to any pointer_type.
  • 从任何 pointer_type 到、、、、、、 sbyte byte short ushort int uint longulongFrom any pointer_type to sbyte, byte, short, ushort, int, uint, long, or ulong.

最后,在不安全的上下文中,标准隐式转换 (标准隐式 转换集) 包括以下指针转换:Finally, in an unsafe context, the set of standard implicit conversions (Standard implicit conversions) includes the following pointer conversion:

  • 从任何 pointer_type 到类型 void*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. 通常,"正确对齐" 这一概念是可传递的:如果指向类型的指针 A 正确地对齐了指向类型的指针,而后者又为指向类型的指针正确对齐,则指向该 B 类型的 C 指针 A 会正确对齐指向类型的指针 CIn 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. 例如,下面的方法以十六进制值的形式显示 double 中的八个字节中的每个字节: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();
        }
    }
}

当然,生成的输出取决于 endian。Of course, the output produced depends on endianness.

指针和整数之间的映射是实现定义的。Mappings between pointers and integers are implementation-defined. 但是,在带有线性地址空间的 32 * 和64位 CPU 体系结构上,转换到整数类型或从整型转换到整数类型的方式通常与 uint ulong 在这些整型类型之间的转换或值完全相同。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:

  • 隐式引用转换 (从任意 array_type 到) 的 隐式引用转换 System.Array ,并且它实现的接口也适用于指针数组。The implicit reference conversion (Implicit reference conversions) from any array_type to System.Array and the interfaces it implements also applies to pointer arrays. 但是,只要尝试通过或其实现的接口访问数组元素,就 System.Array 会导致运行时出现异常,因为指针类型不能转换为 objectHowever, 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.
  • 隐式和显式引用转换 (隐式引用转换、从一维数组类型到的 显式引用转换 S[] System.Collections.Generic.IList<T> 和泛型基接口) 从不适用于指针数组,因为指针类型不能用作类型参数,并且不能从指针类型转换为非指针类型。The implicit and explicit reference conversions (Implicit reference conversions, Explicit reference conversions) 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.
  • 显式引用转换 (从) 的 显式引用 转换 System.Array ,并且它为任何 array_type 实现的接口适用于指针数组。The explicit reference conversion (Explicit reference conversions) from System.Array and the interfaces it implements to any array_type applies to pointer arrays.
  • 显式引用转换 (从) 的 显式引用转换 System.Collections.Generic.IList<S> 和其基接口到一维数组类型不 T[] 应用于指针数组,因为指针类型不能用作类型参数,并且不能从指针类型转换为非指针类型。The explicit reference conversions (Explicit reference conversions) 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.

这些限制意味着 foreach foreach 语句 中所述的对数组的语句扩展不能应用于指针数组。These restrictions mean that the expansion for the foreach statement over arrays described in The foreach statement cannot be applied to pointer arrays. 相反,窗体的 foreach 语句Instead, a foreach statement of the form

foreach (V v in x) embedded_statement

其中,的类型 x 为形式的数组类型 T[,,...,]N 为维数减1, TV 为指针类型,使用嵌套的 for 循环展开,如下所示: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;
    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 = (V)a.GetValue(i0,i1,...,iN);
        embedded_statement
    }
}

变量 ai0 ,, i1 ...)对 iN xembedded_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. 此变量 v 在嵌入语句中是只读的。The variable v is read-only in the embedded statement. 如果没有显式转换 (指针转换) T (元素类型) 到之间 V ,则会生成错误,并且不会执行任何其他步骤。If there is not an explicit conversion (Pointer conversions) from T (the element type) to V, an error is produced and no further steps are taken. 如果 x 具有值 nullSystem.NullReferenceException 则会在运行时引发。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. 在不安全的上下文中,如果有任何 simple_name (简单名称) 、 member_access (成员访问) 、invocation_expression () element_access () 元素访问,则会发生编译时错误。In precise terms, outside an unsafe context a compile-time error occurs if any simple_name (Simple names), member_access (Member access), invocation_expression (Invocation expressions), or element_access (Element access) is of a pointer type.

在不安全的上下文中, primary_no_array_creation_expression (主表达式) 并 Unary_expression (一元运算符) 生产允许以下附加构造:In an unsafe context, the primary_no_array_creation_expression (Primary expressions) and unary_expression (Unary operators) productions permit the following additional constructs:

primary_no_array_creation_expression_unsafe
    : pointer_member_access
    | pointer_element_access
    | sizeof_expression
    ;

unary_expression_unsafe
    : pointer_indirection_expression
    | addressof_expression
    ;

以下各节介绍了这些构造。These constructs are described in the following sections. 语法暗示了 unsafe 运算符的优先级和关联性。The precedence and associativity of the unsafe operators is implied by the grammar.

指针间接Pointer indirection

Pointer_indirection_expression 包含星号 (*) 后跟 unary_expressionA 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. 计算结果 *P (其中 P 是指针类型的表达式) T* 是类型的变量 TThe result of evaluating *P, where P is an expression of a pointer type T*, is a variable of type T. 将一元 * 运算符应用到类型的表达式 void* 或不是指针类型的表达式时,会发生编译时错误。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.

向指针应用一元运算符的效果 * null 是实现定义的。The effect of applying the unary * operator to a null pointer is implementation-defined. 特别是,不能保证此操作引发 System.NullReferenceExceptionIn 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 Pointer conversions), and the address of a variable after the end of its lifetime.

出于明确的赋值分析目的,通过对窗体的表达式进行计算而生成的变量 *P 被视为初始分配 (初始分配的变量) 。For purposes of definite assignment analysis, a variable produced by evaluating an expression of the form *P is considered initially assigned (Initially assigned variables).

指针成员访问Pointer member access

Pointer_member_access 包含一个 primary_expression,后跟一个 " -> " 标记,后跟一个 标识符 和一个可选的 type_argument_listA 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
    ;

在窗体的指针成员访问中 P->IP 必须是指针类型的表达式,而不是 void* ,并且 I 必须表示指向的类型的可访问成员 PIn 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.

窗体的指针成员访问权限 P->I 完全相同 (*P).IA pointer member access of the form P->I is evaluated exactly as (*P).I. 有关指针间接寻址运算符的说明 (*) ,请参阅 指针间接寻址For a description of the pointer indirection operator (*), see Pointer indirection. 有关 () 成员访问运算符的说明 . ,请参阅 成员访问权限For a description of the member access operator (.), see Member access.

示例中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. 由于此操作 P->I 完全等效于 (*P).I ,因此,该 Main 方法的编写方法可能同样适用: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

Pointer_element_access 包含一个 primary_no_array_creation_expression 后跟一个括在 " [ " 和 "" 中的表达式 ]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 ']'
    ;

在窗体的指针元素访问中 P[E]P 必须是指针类型的表达式,而不是 void* ,并且 E 必须是可隐式转换为 intuint 、或的表达式 long ulongIn 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.

对窗体的指针元素的访问权限 P[E] 完全相同 *(P + E)A pointer element access of the form P[E] is evaluated exactly as *(P + E). 有关指针间接寻址运算符的说明 (*) ,请参阅 指针间接寻址For a description of the pointer indirection operator (*), see Pointer indirection. 有关指针加法运算符 () 的说明 + ,请参阅 指针算法For a description of the pointer addition operator (+), see Pointer arithmetic.

示例中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;
        }
    }
}

指针元素访问用于在循环中初始化字符缓冲区 fora pointer element access is used to initialize the character buffer in a for loop. 由于此操作 P[E] 完全等效于 *(P + E) ,因此,该示例也可以正确编写: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. 这与 C 和 c + + 相同。This is the same as C and C++.

address-of 运算符The address-of operator

Addressof_expression 包含一个与号 (&) 后跟一个 unary_expressionAn addressof_expression consists of an ampersand (&) followed by a unary_expression.

addressof_expression
    : '&' unary_expression
    ;

给定一个类型为的表达式, E 该表达式 T 归类为固定变量 (固定和可移动的变量) ,构造 &E 计算给定的变量的地址 EGiven an expression E which is of a type T and is classified as a fixed variable (Fixed and moveable variables), the construct &E computes the address of the variable given by E. 结果的类型为 T* ,并归类为值。The type of the result is T* and is classified as a value. 如果未分类为变量,则会发生编译时错误 E ,如果归类为只读局部变量,则会发生编译时错误; E 如果 E 表示可移动变量,则会发生编译时错误。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. 在最后一种情况下,fixed 语句 (固定 语句) 可用于在获取其地址之前暂时 "修复" 该变量。In the last case, a fixed statement (The fixed statement) can be used to temporarily "fix" the variable before obtaining its address. 成员访问中所述,在定义字段的结构或类的实例构造函数或静态构造函数外部, readonly 该字段被视为值,而不是变量。As stated in Member access, 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 在用于初始化的操作后被视为已明确赋值 &i pi is considered definitely assigned following the &i operation used to initialize p. 中的赋值将进行 *p 初始化 i ,但包含此初始化是程序员的责任,并且如果删除分配,则不会发生编译时错误。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. 例如,许多外部 Api 采用一个指向结构的指针,该结构由 API 填充。For example, many external APIs take a pointer to a structure which is filled in by the API. 调用此类 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.

指针增量和减量Pointer increment and decrement

在不安全的上下文中, ++-- 运算符 (后缀递增和递减运算符 以及 前缀增量和减量运算符) 可应用于除以外的所有类型的指针变量 void*In an unsafe context, the ++ and -- operators (Postfix increment and decrement operators and Prefix increment and decrement operators) can be applied to pointer variables of all types except void*. 因此,对于每个指针类型 T* ,以下运算符都是隐式定义的:Thus, for every pointer type T*, the following operators are implicitly defined:

T* operator ++(T* x);
T* operator --(T* x);

运算符 x + 1 x - 1 分别 (指针算法) 生成与和相同的结果。The operators produce the same results as x + 1 and x - 1, respectively (Pointer arithmetic). 换言之,对于类型为的指针变量, T* ++ 运算符将添加 sizeof(T) 到变量中包含的地址, -- 运算符将从该 sizeof(T) 变量中包含的地址中减去。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 implementation-defined, but no exceptions are produced.

指针算术Pointer arithmetic

在不安全的上下文中, +- 运算符 (加法运算符减法运算符) 可应用于除之外的所有指针类型的值 void*In an unsafe context, the + and - operators (Addition operator and Subtraction operator) can be applied to values of all pointer types except void*. 因此,对于每个指针类型 T* ,以下运算符都是隐式定义的: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);

给定 P 指针类型的表达式 T*N 类型为 int 、、或的表达式时 uint long ulong ,表达式 P + NN + P 计算类型的指针值,该类型是 T* 通过将添加 N * sizeof(T) 到给定的地址来生成的 PGiven 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. 同样,表达式 P - N 会计算类型的指针值 T* ,结果是从给定的地址中减去 N * sizeof(T) PLikewise, the expression P - N computes the pointer value of type T* that results from subtracting N * sizeof(T) from the address given by P.

如果有两个表达式( PQ )作为指针类型 T* ,则表达式将 P - Q 计算与给定的地址之间的差异, P 然后将 Q 该差异除以 sizeof(T)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 that difference by sizeof(T). 结果的类型始终为 longThe type of the result is always long. 实际上, P - Q 将计算为 ((long)(P) - (long)(Q)) / sizeof(T)In effect, P - Q is computed as ((long)(P) - (long)(Q)) / sizeof(T).

例如:For example:

using System;

class Test
{
    static void Main() {
        unsafe {
            int* values = stackalloc int[20];
            int* p = &values[1];
            int* q = &values[15];
            Console.WriteLine("p - q = {0}", p - q);
            Console.WriteLine("q - p = {0}", q - p);
        }
    }
}

生成输出的:which 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 (Relational and type-testing operators) 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);

由于存在从任何指针类型到类型的隐式转换 void* ,因此可以使用这些运算符比较任何指针类型的操作数。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.

Sizeof 运算符The sizeof operator

sizeof 运算符返回给定类型的变量所占用的字节数。The sizeof operator returns the number of bytes occupied by a variable of a given type. 指定为操作数的类型 sizeof 必须是) 的 Unmanaged_type (指针类型The type specified as an operand to sizeof must be an unmanaged_type (Pointer types).

sizeof_expression
    : 'sizeof' '(' unmanaged_type ')'
    ;

运算符的结果 sizeof 为类型的值 intThe result of the sizeof operator is a value of type int. 对于某些预定义类型, sizeof 运算符将生成一个常数值,如下表所示。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

对于所有其他类型,运算符的结果 sizeof 是实现定义的,并归类为值而不是常量。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 members are packed into a struct is unspecified.

出于对齐目的,在结构的开头、结构和结构的末尾,可能有未命名的填充。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.

fixed 语句The fixed statement

在不安全的上下文中,) 生产的 embedded_statement (语句 允许使用其他构造 fixed 语句,该语句用于 "修复" 可移动的变量,以使其地址在语句的持续时间内保持不变。In an unsafe context, the embedded_statement (Statements) 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.

fixed_statement
    : 'fixed' '(' pointer_type fixed_pointer_declarators ')' embedded_statement
    ;

fixed_pointer_declarators
    : fixed_pointer_declarator (','  fixed_pointer_declarator)*
    ;

fixed_pointer_declarator
    : identifier '=' fixed_pointer_initializer
    ;

fixed_pointer_initializer
    : '&' variable_reference
    | expression
    ;

每个 fixed_pointer_declarator 都声明给定 pointer_type 的局部变量,并使用相应 fixed_pointer_initializer 计算出的地址初始化该局部变量。Each fixed_pointer_declarator declares a local variable of the given pointer_type and initializes that local variable with the address computed by the corresponding fixed_pointer_initializer. 语句中声明的局部变量 fixed 可在该变量的声明右侧以及语句的 embedded_statement 中发生的任何 fixed_pointer_initializer 中访问 fixedA local variable declared in a fixed statement is accessible in any fixed_pointer_initializer s occurring to the right of that variable's declaration, and in the embedded_statement of the fixed statement. 语句声明的局部变量 fixed 被视为只读。A local variable declared by a fixed statement is considered read-only. 如果嵌入的语句尝试通过赋值 (修改此局部变量 ++ ,或者 --) 将其作为或参数传递,则会发生编译时错误 ref outA compile-time error occurs if the embedded statement attempts to modify this local variable (via assignment or the ++ and -- operators) or pass it as a ref or out parameter.

Fixed_pointer_initializer 可以是以下项之一:A fixed_pointer_initializer can be one of the following:

  • 标记 " & " 后跟一个 variable_reference (用于确定非托管类型 (固定和) 可移动变量的明确赋值) 的 精确规则 T ,前提是该类型可 T* 隐式转换为语句中给定的指针类型 fixedThe token "&" followed by a variable_reference (Precise rules for determining definite assignment) to a moveable variable (Fixed and moveable variables) of an unmanaged type T, provided the type T* is implicitly convertible to the pointer type given in the fixed statement. 在这种情况下,初始值设定项将计算给定变量的地址,并且在语句的持续时间内保证变量在固定的地址上保持不变 fixedIn 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.
  • 包含非托管类型的元素的 array_type 的表达式 T ,前提是该类型可 T* 隐式转换为语句中给定的指针类型 fixedAn 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. 在这种情况下,初始值设定项计算数组中第一个元素的地址,并且保证整个数组在语句的持续时间内保持为固定的地址 fixedIn 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. 如果数组表达式为 null 或数组包含零个元素,则初始值设定项将计算等于零的地址。If the array expression is null or if the array has zero elements, the initializer computes an address equal to zero.
  • string如果类型可 char* 隐式转换为语句中给定的指针类型,则为类型的表达式 fixedAn expression of type string, provided the type char* is implicitly convertible to the pointer type given in the fixed statement. 在这种情况下,初始值设定项将计算字符串中第一个字符的地址,并且保证整个字符串在语句的持续时间内保持为固定地址 fixedIn 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. fixed如果字符串表达式为 null,则语句的行为是实现定义的。The behavior of the fixed statement is implementation-defined if the string expression is null.
  • 引用可移动变量的固定大小缓冲区成员的 simple_namemember_access ,前提是固定大小缓冲区成员的类型可隐式转换为语句中给定的指针类型 fixedA simple_name or member_access that references a fixed size buffer member of a moveable variable, provided the type of the fixed size buffer member is implicitly convertible to the pointer type given in the fixed statement. 在这种情况下,初始值设定项计算一个指针,该指针指向固定大小缓冲区的第一个元素 () 表达式中的固定 大小缓冲区,并且保证固定大小缓冲区在语句的持续时间内保持为固定的地址 fixedIn this case, the initializer computes a pointer to the first element of the fixed size buffer (Fixed size buffers in expressions), and the fixed size buffer is guaranteed to remain at a fixed address for the duration of the fixed statement.

对于由 fixed_pointer_initializer 计算的每个地址,该 fixed 语句可确保在语句的持续时间内,由垃圾回收器在该地址引用的变量不会重定位或释放 fixedFor 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. 例如,如果由 fixed_pointer_initializer 计算的地址引用一个对象的字段或数组实例的一个元素,则该 fixed 语句保证在语句的生存期内不会重新定位或释放包含对象实例。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.

编程人员应负责确保由语句创建的指针 fixed 不会在执行这些语句的范围之外。It is the programmer's responsibility to ensure that pointers created by fixed statements do not survive beyond execution of those statements. 例如,当语句创建的指针 fixed 被传递给外部 api 时,程序员应负责确保 api 不保留这些指针的内存。For example, 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.

固定对象可能会导致堆 (碎片,因为它们不能) 移动。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

class Test
{
    static int x;
    int y;

    unsafe static void F(int* p) {
        *p = 1;
    }

    static void Main() {
        Test t = new Test();
        int[] a = new int[10];
        unsafe {
            fixed (int* p = &x) F(p);
            fixed (int* p = &t.y) F(p);
            fixed (int* p = &a[0]) F(p);
            fixed (int* p = a) F(p);
        }
    }
}

演示语句的几次使用 fixeddemonstrates 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. 在每种情况下,使用 regular 运算符都是错误的, & 因为变量全都归类为可移动变量。In each case it would have been an error to use the regular & operator since the variables are all classified as moveable variables.

上面示例中的第四个 fixed 语句生成的结果类似于第三个。The fourth fixed statement in the example above produces a similar result to the third.

此语句的示例 fixed 使用 stringThis example of the fixed statement uses string:

class Test
{
    static string name = "xx";

    unsafe static void F(char* p) {
        for (int i = 0; p[i] != '\0'; ++i)
            Console.WriteLine(p[i]);
    }

    static void Main() {
        unsafe {
            fixed (char* p = name) F(p);
            fixed (char* p = "xx") F(p);
        }
    }
}

在不安全的上下文数组元素中,一维数组的元素以递增索引顺序存储,以 index 开头, 0 以 index 结尾 Length - 1In an unsafe context array elements of single-dimensional arrays are stored in increasing index order, starting with index 0 and ending with index Length - 1. 对于多维数组,将存储数组元素,这样最右边的维度的索引将首先增加,然后是下一个左侧维度,依此类推。For multi-dimensional arrays, array elements are stored such that the indices of the rightmost dimension are increased first, then the next left dimension, and so on to the left. fixed 获取指向数组实例的指针的语句中 p a ,从到的指针值 p 用于 p + a.Length - 1 表示数组中元素的地址。Within a fixed statement that obtains a pointer p to an array instance a, the pointer values ranging from p to p + a.Length - 1 represent addresses of the elements in the array. 同样,范围内的变量 p[0] 用于 p[a.Length - 1] 表示实际数组元素。Likewise, the variables ranging from p[0] to p[a.Length - 1] represent the actual array elements. 考虑到数组的存储方式,可以将任何维度的数组视为线性数组。Given the way in which arrays are stored, we can treat an array of any dimension as though it were linear.

例如:For example:

using System;

class Test
{
    static void Main() {
        int[,,] a = new int[2,3,4];
        unsafe {
            fixed (int* p = a) {
                for (int i = 0; i < a.Length; ++i)    // treat as linear
                    p[i] = i;
            }
        }

        for (int i = 0; i < 2; ++i)
            for (int j = 0; j < 3; ++j) {
                for (int k = 0; k < 4; ++k)
                    Console.Write("[{0},{1},{2}] = {3,2} ", i, j, k, a[i,j,k]);
                Console.WriteLine();
            }
    }
}

生成输出的:which 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

class Test
{
    unsafe static void Fill(int* p, int count, int value) {
        for (; count != 0; count--) *p++ = value;
    }

    static void Main() {
        int[] a = new int[100];
        unsafe {
            fixed (int* p = a) Fill(p, 100, -1);
        }
    }
}

fixed语句用于修复数组,以便可以将其地址传递给采用指针的方法。a fixed statement is used to fix an array so its address can be passed to a method that takes a pointer.

在示例中:In the example:

unsafe struct Font
{
    public int size;
    public fixed char name[32];
}

class Test
{
    unsafe static void PutString(string s, char* buffer, int bufSize) {
        int len = s.Length;
        if (len > bufSize) len = bufSize;
        for (int i = 0; i < len; i++) buffer[i] = s[i];
        for (int i = len; i < bufSize; i++) buffer[i] = (char)0;
    }

    Font f;

    unsafe static void Main()
    {
        Test test = new Test();
        test.f.size = 10;
        fixed (char* p = test.f.name) {
            PutString("Times New Roman", p, 32);
        }
    }
}

fixed 语句用于修复结构的固定大小缓冲区,使其地址可用作指针。a fixed statement is used to fix a fixed size buffer of a struct so its address can be used as a pointer.

char*通过修复字符串实例生成的值始终指向以 null 结尾的字符串。A char* value produced by fixing a string instance always points to a null-terminated string. 在获取指向字符串实例的指针的 fixed 语句中 p s ,从到的指针值 p 用于 p + s.Length - 1 表示字符串中的字符的地址,并且指针值 p + s.Length 始终指向 null 字符, (值为) 的字符 '\0'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').

通过固定指针修改托管类型的对象可能导致未定义的行为。Modifying objects of managed type through fixed pointers can results in undefined behavior. 例如,因为字符串是不可变的,所以,程序员应负责确保不会修改指向固定字符串的指针所引用的字符。For example, 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.

在调用需要 "C 样式" 字符串的外部 Api 时,字符串的自动 null 终止非常方便。The automatic null-termination of strings is particularly convenient when calling external APIs that expect "C-style" strings. 但请注意,字符串实例允许包含 null 字符。Note, however, that a string instance is permitted to contain null characters. 如果存在这样的空字符,则在被视为以 null 结尾的情况下,该字符串将会被截断 char*If such null characters are present, the string will appear truncated when treated as a null-terminated char*.

固定大小的缓冲区Fixed size buffers

固定大小的缓冲区用于将 "C 样式" 内联数组声明为结构的成员,主要用于与非托管 Api 建立交互。Fixed size buffers are used to declare "C style" in-line arrays as members of structs, and are primarily useful for interfacing with unmanaged APIs.

固定大小的缓冲区声明Fixed size buffer declarations

固定大小缓冲区 是表示给定类型的变量的固定长度缓冲区存储的成员。A fixed size buffer is a member that represents storage for a fixed length buffer of variables of a given type. 固定大小的缓冲区声明引入给定元素类型的一个或多个固定大小的缓冲区。A fixed size buffer declaration introduces one or more fixed size buffers of a given element type. 固定大小的缓冲区只允许在结构声明中出现,并且只能在不安全的上下文中出现 (不安全上下文) 。Fixed size buffers are only permitted in struct declarations and can only occur in unsafe contexts (Unsafe contexts).

struct_member_declaration_unsafe
    : fixed_size_buffer_declaration
    ;

fixed_size_buffer_declaration
    : attributes? fixed_size_buffer_modifier* 'fixed' buffer_element_type fixed_size_buffer_declarator+ ';'
    ;

fixed_size_buffer_modifier
    : 'new'
    | 'public'
    | 'protected'
    | 'internal'
    | 'private'
    | 'unsafe'
    ;

buffer_element_type
    : type
    ;

fixed_size_buffer_declarator
    : identifier '[' constant_expression ']'
    ;

固定大小的缓冲区声明可以包含一组属性, (特性) 、 new 修饰符 (修饰符) 、四个访问修饰符 (类型参数和约束 的有效组合) 和 unsafe 修饰符 (不安全的上下文) 。A fixed size buffer declaration may include a set of attributes (Attributes), a new modifier (Modifiers), a valid combination of the four access modifiers (Type parameters and constraints) and an unsafe modifier (Unsafe contexts). 特性和修饰符适用于由固定大小缓冲区声明声明的所有成员。The attributes and modifiers apply to all of the members declared by the fixed size buffer declaration. 同一修饰符在固定大小的缓冲区声明中多次出现是错误的。It is an error for the same modifier to appear multiple times in a fixed size buffer declaration.

固定大小的缓冲区声明不允许包含 static 修饰符。A fixed size buffer declaration is not permitted to include the static modifier.

固定大小缓冲区声明的 buffer 元素类型指定由声明引入) (s 的元素类型。The buffer element type of a fixed size buffer declaration specifies the element type of the buffer(s) introduced by the declaration. Buffer 元素类型必须是预定义的类型之一:、、、、、、、、、 sbyte byte short ushort int uint long ulong char float doubleboolThe buffer element type must be one of the predefined types sbyte, byte, short, ushort, int, uint, long, ulong, char, float, double, or bool.

Buffer 元素类型后跟固定大小缓冲区声明符的列表,其中每个声明符都引入一个新成员。The buffer element type is followed by a list of fixed size buffer declarators, each of which introduces a new member. 固定大小的缓冲区声明符包含命名成员的标识符,后跟括在和标记中的常量 [ 表达式 ]A fixed size buffer declarator consists of an identifier that names the member, followed by a constant expression enclosed in [ and ] tokens. 常数表达式表示该固定大小缓冲区声明符引入的成员中的元素数目。The constant expression denotes the number of elements in the member introduced by that fixed size buffer declarator. 常数表达式的类型必须可隐式转换为类型 int ,并且值必须为非零正整数。The type of the constant expression must be implicitly convertible to type int, and the value must be a non-zero positive integer.

保证固定大小缓冲区的元素在内存中按顺序排列。The elements of a fixed size buffer are guaranteed to be laid out sequentially in memory.

声明多个固定大小缓冲区的固定大小缓冲区声明等效于具有相同属性和元素类型的单个固定大小缓冲区声明的多个声明。A fixed size buffer declaration that declares multiple fixed size buffers is equivalent to multiple declarations of a single fixed size buffer declaration with the same attributes, and element types. 例如For example

unsafe struct A
{
   public fixed int x[5], y[10], z[100];
}

等效于is equivalent to

unsafe struct A
{
   public fixed int x[5];
   public fixed int y[10];
   public fixed int z[100];
}

表达式中固定大小的缓冲区Fixed size buffers in expressions

固定大小缓冲区成员的成员查找 (运算符) 的执行方式与字段的成员查找完全相同。Member lookup (Operators) of a fixed size buffer member proceeds exactly like member lookup of a field.

可以使用 simple_name (类型推理) 或 member_access (对 动态重载决策) 编译时检查 的表达式中引用固定大小的缓冲区。A fixed size buffer can be referenced in an expression using a simple_name (Type inference) or a member_access (Compile-time checking of dynamic overload resolution).

当将固定大小的缓冲区成员作为简单名称引用时,其效果与窗体的成员访问相同 this.I ,其中 I 是固定大小的缓冲区成员。When a fixed size buffer member is referenced as a simple name, the effect is the same as a member access of the form this.I, where I is the fixed size buffer member.

在窗体的成员访问中 E.I ,如果 E 属于结构类型,并且 I 该结构类型中的成员查找标识固定大小成员,则 E.I 将按如下方式对其进行评估:In a member access of the form E.I, if E is of a struct type and a member lookup of I in that struct type identifies a fixed size member, then E.I is evaluated an classified as follows:

  • 如果表达式 E.I 未出现在不安全的上下文中,则会发生编译时错误。If the expression E.I does not occur in an unsafe context, a compile-time error occurs.
  • 如果 E 归类为值,则会发生编译时错误。If E is classified as a value, a compile-time error occurs.
  • 否则,如果 E 是可移动变量 (固定和可移动变量) 并且该表达式 E.I 不是 (Fixed 语句) 的 fixed_pointer_initializer ,则会发生编译时错误。Otherwise, if E is a moveable variable (Fixed and moveable variables) and the expression E.I is not a fixed_pointer_initializer (The fixed statement), a compile-time error occurs.
  • 否则, E 引用一个固定变量,并且该表达式的结果是指向中的固定大小缓冲区成员的第一个元素的 I 指针 EOtherwise, E references a fixed variable and the result of the expression is a pointer to the first element of the fixed size buffer member I in E. 结果的类型为 S* ,其中 S 是的元素类型 I ,并归类为值。The result is of type S*, where S is the element type of I, and is classified as a value.

可以使用第一个元素的指针操作访问固定大小缓冲区的后续元素。The subsequent elements of the fixed size buffer can be accessed using pointer operations from the first element. 与对数组的访问不同,对固定大小缓冲区的元素的访问是不安全的操作,并且不会进行范围检查。Unlike access to arrays, access to the elements of a fixed size buffer is an unsafe operation and is not range checked.

下面的示例声明并使用具有固定大小缓冲区成员的结构。The following example declares and uses a struct with a fixed size buffer member.

unsafe struct Font
{
    public int size;
    public fixed char name[32];
}

class Test
{
    unsafe static void PutString(string s, char* buffer, int bufSize) {
        int len = s.Length;
        if (len > bufSize) len = bufSize;
        for (int i = 0; i < len; i++) buffer[i] = s[i];
        for (int i = len; i < bufSize; i++) buffer[i] = (char)0;
    }

    unsafe static void Main()
    {
        Font f;
        f.size = 10;
        PutString("Times New Roman", f.name, 32);
    }
}

明确赋值检查Definite assignment checking

固定大小的缓冲区不受明确赋值检查 (明确 赋值) ,并且忽略固定大小的缓冲区成员以便对结构类型变量进行明确的赋值检查。Fixed size buffers are not subject to definite assignment checking (Definite assignment), and fixed size buffer members are ignored for purposes of definite assignment checking of struct type variables.

当包含固定大小缓冲区成员的最外面的包含结构变量是静态变量、类实例的实例变量或数组元素时,固定大小缓冲区的元素会自动初始化为其默认值 (默认值) 。When the outermost containing struct variable of a fixed size buffer member is a static variable, an instance variable of a class instance, or an array element, the elements of the fixed size buffer are automatically initialized to their default values (Default values). 在所有其他情况下,固定大小缓冲区的初始内容是不确定的。In all other cases, the initial content of a fixed size buffer is undefined.

堆栈分配Stack allocation

在不安全的上下文中,局部变量声明 (局部变量 声明,) 可能包括从调用堆栈中分配内存的堆栈分配初始值设定项。In an unsafe context, a local variable declaration (Local variable declarations) may include a stack allocation initializer which allocates memory from the call stack.

local_variable_initializer_unsafe
    : stackalloc_initializer
    ;

stackalloc_initializer
    : 'stackalloc' unmanaged_type '[' expression ']'
    ;

Unmanaged_type 指示将存储在新分配的位置的项的类型,并且 表达式 指示这些项的数目。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. 由于堆栈分配的大小不能为负,因此将项的数目指定为计算结果为负值的 constant_expression 是编译时错误。Since the size of a stack allocation cannot be negative, it is a compile-time error to specify the number of items as a constant_expression that evaluates to a negative value.

窗体的堆栈分配初始值设定 stackalloc T[E]T 必须为非托管类型 (指针类型) 并为 E 类型的表达式 intA stack allocation initializer of the form stackalloc T[E] requires T to be an unmanaged type (Pointer types) and E to be an expression of type int. 构造 E * sizeof(T) 从调用堆栈分配字节,并将类型的指针返回 T* 到新分配的块。The construct allocates E * sizeof(T) bytes from the call stack and returns a pointer, of type T*, to the newly allocated block. 如果 E 是负值,则行为是不确定的。If E is a negative value, then the behavior is undefined. 如果 E 为零,则不进行任何分配,并且返回的指针是实现定义的。If E is zero, then no allocation is made, and the pointer returned is implementation-defined. 如果内存不足,无法分配指定大小的块, System.StackOverflowException 则会引发。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.

catch finally try 语句) (或块中不允许使用堆栈分配初始值设定项。Stack allocation initializers are not permitted in catch or finally blocks (The try statement).

无法显式释放使用分配 stackalloc 的内存。There is no way to explicitly free memory allocated using stackalloc. 在函数成员执行过程中创建的所有堆栈分配的内存块将在该函数成员返回时自动被丢弃。All stack allocated memory blocks created during the execution of a function member are automatically discarded when that function member returns. 这对应于 alloca 函数,这是一个在 C 和 c + + 实现中常见的扩展。This corresponds to the alloca function, an extension commonly found in C and C++ implementations.

示例中In the example

using System;

class Test
{
    static string IntToString(int value) {
        int n = value >= 0? value: -value;
        unsafe {
            char* buffer = stackalloc char[16];
            char* p = buffer + 16;
            do {
                *--p = (char)(n % 10 + '0');
                n /= 10;
            } while (n != 0);
            if (value < 0) *--p = '-';
            return new string(p, 0, (int)(buffer + 16 - p));
        }
    }

    static void Main() {
        Console.WriteLine(IntToString(12345));
        Console.WriteLine(IntToString(-999));
    }
}

stackalloc在方法中使用初始值设定项在 IntToString 堆栈上分配16个字符的缓冲区。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

除了运算符以外 stackalloc ,c # 不提供用于管理非垃圾回收内存的预定义构造。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. 例如,下面的 Memory 类演示了如何从 c # 访问基础操作系统的堆函数:For example, the Memory class below illustrates how the heap functions of an underlying operating system might be accessed from C#:

using System;
using System.Runtime.InteropServices;

public static unsafe class Memory
{
    // Handle for the process heap. This handle is used in all calls to the
    // HeapXXX APIs in the methods below.
    private static readonly IntPtr s_heap = GetProcessHeap();

    // Allocates a memory block of the given size. The allocated memory is
    // automatically initialized to zero.
    public static void* Alloc(int size)
    {
        void* result = HeapAlloc(s_heap, HEAP_ZERO_MEMORY, (UIntPtr)size);
        if (result == null) throw new OutOfMemoryException();
        return result;
    }

    // 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)
    {
        byte* ps = (byte*)src;
        byte* pd = (byte*)dst;
        if (ps > pd)
        {
            for (; count != 0; count--) *pd++ = *ps++;
        }
        else if (ps < pd)
        {
            for (ps += count, pd += count; count != 0; count--) *--pd = *--ps;
        }
    }

    // Frees a memory block.
    public static void Free(void* block)
    {
        if (!HeapFree(s_heap, 0, block)) throw new InvalidOperationException();
    }

    // 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)
    {
        void* result = HeapReAlloc(s_heap, HEAP_ZERO_MEMORY, block, (UIntPtr)size);
        if (result == null) throw new OutOfMemoryException();
        return result;
    }

    // Returns the size of a memory block.
    public static int SizeOf(void* block)
    {
        int result = (int)HeapSize(s_heap, 0, block);
        if (result == -1) throw new InvalidOperationException();
        return result;
    }

    // Heap API flags
    private const int HEAP_ZERO_MEMORY = 0x00000008;

    // Heap API functions
    [DllImport("kernel32")]
    private static extern IntPtr GetProcessHeap();

    [DllImport("kernel32")]
    private static extern void* HeapAlloc(IntPtr hHeap, int flags, UIntPtr size);

    [DllImport("kernel32")]
    private static extern bool HeapFree(IntPtr hHeap, int flags, void* block);

    [DllImport("kernel32")]
    private static extern void* HeapReAlloc(IntPtr hHeap, int flags, void* block, UIntPtr size);

    [DllImport("kernel32")]
    private static extern UIntPtr HeapSize(IntPtr hHeap, int flags, void* block);
}

Memory下面给出了使用类的示例:An example that uses the Memory class is given below:

class Test
{
    static unsafe void Main()
    {
        byte* buffer = null;
        try
        {
            const int Size = 256;
            buffer = (byte*)Memory.Alloc(Size);
            for (int i = 0; i < Size; i++) buffer[i] = (byte)i;
            byte[] array = new byte[Size];
            fixed (byte* p = array) Memory.Copy(buffer, p, Size);
            for (int i = 0; i < Size; i++) Console.WriteLine(array[i]);
        }
        finally
        {
            if (buffer != null) Memory.Free(buffer);
        }
    }
}

该示例通过分配256字节的内存 Memory.Alloc ,并初始化值从0增加到255的内存块。The example allocates 256 bytes of memory through Memory.Alloc and initializes the memory block with values increasing from 0 to 255. 然后,它分配一个256元素字节数组,并使用将 Memory.Copy 内存块的内容复制到字节数组中。It then allocates a 256 element byte array and uses Memory.Copy to copy the contents of the memory block into the byte array. 最后,使用释放内存块, Memory.Free 并在控制台上输出字节数组的内容。Finally, the memory block is freed using Memory.Free and the contents of the byte array are output on the console.