Unsafe 程式碼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++程式的整個錯誤類別會被排除。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#提供了撰寫 unsafe 程式代碼的功能。To address this need, C# provides the ability to write unsafe code.

在 unsafe 程式碼中,您可以宣告和操作指標、執行指標和整數類型之間的轉換、接受變數的位址等等。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. 就意義而言,撰寫 unsafe 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 內容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.
  • 欄位、方法、屬性、事件、索引子、運算子、實例的程式化、析構函數或靜態的函式的宣告可能包含 @no__t 0 修飾詞,在這種情況下,該成員宣告的整個文字範圍會被視為不安全的內容。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. 因此,您可以將 Left 和 @no__t 1 欄位宣告為指標類型。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();
        ...
    }
}

AF 方法上的 unsafe 修飾詞,只會使 F 的文字範圍成為不安全的內容,而不會使用語言的 unsafe 功能。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. BF 覆寫中,不需要重新指定 unsafe 修飾詞,除非在 B 中的 F 方法本身需要存取不安全的功能。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 修飾詞來引進,如同 B 中的情況。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

在不安全的內容中,類型類型)可能是pointer_type ,也可以是value_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也可用於不安全內容外的 @no__t 1 運算式(匿名物件建立運算式),因為這種用法並不安全。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_type會寫成unmanaged_type或關鍵字 void,後面接著 * token: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_type是不是reference_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:

  • sbytebyteshortushortintuintlongulongcharfloat、0、1 或 2。sbyte, 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_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* @No__t 的指標-0Pointer to byte
char* @No__t 的指標-0Pointer to char
int** 指標指向 intPointer to pointer to int
int*[] @No__t-0 的一維指標陣列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.

不同于 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;

類型為 T* 的指標值表示 T 類型的變數位址。The 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. 例如,假設 int* 類型的變數 P,則運算式 *P 表示在 P 中所包含的位址上找到的 int 變數。For 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. 將間接運算子套用至 @no__t 0 指標會導致實作為定義的行為。Applying the indirection operator to a null pointer results in implementation-defined behavior. 值為 null 的指標是以全位-零表示。A pointer with value null is represented by all-bits-zero.

@No__t-0 類型代表未知類型的指標。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,而且指標類型和 object 之間不會有任何轉換。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 (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可以當做 volatile 欄位(volatile 欄位)的類型使用。A pointer_type may be used as the type of a volatile field (Volatile fields).

雖然指標可以做為 ref 或 @no__t 1 參數傳遞,但這麼做可能會造成未定義的行為,因為指標可能會設定為指向在呼叫的方法傳回時不再存在的區域變數,或其用來指向的固定物件。不再是固定的。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 運算子(「位址」運算子)和 @no__t 1 語句(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.)

@No__t-0 運算子(通訊運算子)允許取得固定變數的位址,而不受限制。The & operator (The address-of operator) permits the address of a fixed variable to be obtained without restrictions. 不過,因為可移動變數可能會由垃圾收集行程重新配置或處置,所以可移動變數的位址只能使用 fixed 語句(fixed 語句)取得,而且該位址只會針對@no__t 2 語句的持續時間。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 (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.
  • V.I 格式的member_access成員存取)所產生的變數,其中 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指標間接取值)形成的變數,其格式為 *P、格式為 P->Ipointer_member_access指標成員存取),或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. 另請注意,即使為參數提供的引數是固定的變數,@no__t 0 或 @no__t 1 參數也會分類為可移動變數。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.
  • sbytebyteshortushortintuintlong,或 ulong 到任何pointer_typeFrom sbyte, byte, short, ushort, int, uint, long, or ulong to any pointer_type.
  • 從任何pointer_typesbytebyteshortushortintuintlongulongFrom 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

當指標類型轉換成 byte 的指標時,結果會指向變數的最低定址位元組。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 或 @no__t 1 值的轉換完全相同。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_typeSystem.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 語句中所描述的陣列上,@no__t 0 語句的展開無法套用至指標陣列。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 是指標類型,則會使用 nested 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
    }
}

@No__t-0、`i0`、`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 的值 null,則會在執行時間擲回 System.NullReferenceExceptionIf 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*,是 T 類型的變數。The 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.

將一元 * 運算子套用至 @no__t 1 指標的效果是由實作為定義的。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->I 的指標成員存取中,P 必須是 void* 以外指標類型的運算式,而且 I 必須代表 P 點之類型的可存取成員。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.

@No__t-0 格式的指標成員存取,會完全依照 (*P).I 進行評估。A 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,所以已撰寫 @no__t 2 方法: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 必須是可以隱含轉換成 intuintlongulong 的運算式。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.

@No__t-0 格式的指標專案存取,會完全依照 *(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;
        }
    }
}

指標專案存取是用來初始化 for 迴圈中的字元緩衝區。a 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 運算子The address-of operator

Addressof_expression是由後面接著unary_expression的連字號(&)所組成。An addressof_expression consists of an ampersand (&) followed by a unary_expression.

addressof_expression
    : '&' unary_expression
    ;

假設有一個運算式 E,這種類型 T,並分類為固定變數(固定和可移動的變數),則結構 &E 會計算 E 所提供的變數位址。Given 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. 成員存取中所述,在定義 @no__t 1 欄位的結構或類別之外,在實例的參數或靜態的函式以外,該欄位會被視為值,而不是變數。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.

@No__t-0 運算子不需要明確指派其引數,但在 & 作業之後,套用運算子的變數會被視為在作業發生所在的執行路徑中明確指派。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 會被視為依照用來初始化 p&i 作業進行明確指派。i 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.

@No__t-0 運算子的明確指派規則存在,因此可以避免區域變數的重複初始化。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

在不安全的內容中,++ 和 @no__t 1 運算子(後置遞增和遞減運算子前置遞增和遞減運算子)可以套用至所有類型的指標變數,但不包括 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 + 1x - 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

在不安全的內容中,+ 和 @no__t 1 運算子(加法運算子減法運算子)可以套用至所有指標類型的值,但 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* 和一個運算式 @no__t intuintlongulong,則運算式 P + NN + P 會計算 T* 類型的指標值,而結果是將 @no__t 新增至位址提供者為 1。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. 同樣地,運算式 P - N 會計算 T* 類型的指標值,這是從 P 所指定的位址減去 N * sizeof(T) 所產生的。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.

假設有兩個運算式(PQ)的指標類型 T*,運算式 P - Q 會計算 PQ 所指定的位址之間的差異,然後將該差異除以 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

在不安全的內容中,==!=<><= 和 @no__t 5 運算子(關聯式和類型測試運算子)可以套用至所有指標類型的值。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 ')'
    ;

@No__t-0 運算子的結果是 int 類型的值。The 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 語句中宣告的區域變數,可以在該變數宣告的右邊發生的任何fixed_pointer_initializer中,以及在 @no__t 3 語句的embedded_statement中存取。A local variable declared in a fixed statement is accessible in any fixed_pointer_initializers occurring to the right of that variable's declaration, and in the embedded_statement of the fixed statement. 由 @no__t 0 的語句所宣告的區域變數會被視為唯讀。A local variable declared by a fixed statement is considered read-only. 如果內嵌語句嘗試修改這個本機變數(透過指派或 ++ 和 @no__t 1 運算子),或將它當做 refout 參數傳遞,就會發生編譯時期錯誤。A 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:

  • Token "&" 後面接著variable_reference判斷明確指派的精確規則T 的非受控類型的可移動變數(固定和可移動變數),前提是 T* 的類型為可隱含轉換為 fixed 語句中提供的指標類型。The 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. 在此情況下,初始化運算式會計算給定變數的位址,並保證在 fixed 語句期間,變數會保留在固定位址。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.
  • Array_type的運算式,其中包含非受控類型的專案 T,前提是類型 T* 可以隱含地轉換成 fixed 語句中提供的指標類型。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. 在此情況下,初始化運算式會計算陣列中第一個元素的位址,而整個陣列保證會在 fixed 語句期間保持固定位址。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. 如果陣列運算式為 null,或者陣列有零個元素,則初始化運算式會將位址計算為等於零。If the array expression is null or if the array has zero elements, the initializer computes an address equal to zero.
  • 類型為 string 的運算式,但前提是類型 char* 可以隱含地轉換成 fixed 語句中提供的指標類型。An expression of type string, provided the type char* is implicitly convertible to the pointer type given in the fixed statement. 在此情況下,初始化運算式會計算字串中第一個字元的位址,而在 fixed 語句期間,整個字串保證會保留在固定位址。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. 如果字串運算式為 null,則 fixed 語句的行為會定義為「執行中」。The behavior of the fixed statement is implementation-defined if the string expression is null.
  • Simple_namemember_access ,參考可移動變數的固定大小緩衝區成員,前提是固定大小緩衝區成員的類型可以隱含地轉換成在 fixed 語句中指定的指標類型。A 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. 在此情況下,初始化運算式會計算固定大小緩衝區之第一個元素的指標(運算式中的固定大小緩衝區),而固定大小的緩衝區保證會在 fixed 語句期間保留在固定位址。In 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 語句可確保在 @no__t 2 語句期間,垃圾收集行程不會重新配置或處置位址所參考的變數。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. 例如,如果fixed_pointer_initializer所計算的位址參考了物件的欄位或陣列實例的元素,則 @no__t 1 語句可保證包含的物件實例在執行期間不會重新置放或處置語句的存留期。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.

程式設計人員必須負責確保 @no__t 0 語句所建立的指標不會在執行這些語句之後存留下來。It is the programmer's responsibility to ensure that pointers created by fixed statements do not survive beyond execution of those statements. 例如,當由 @no__t 0 的語句所建立的指標傳遞給外部 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);
        }
    }
}

示範 fixed 語句的數種用法。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.

上述範例中的第四個 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);
        }
    }
}

在一維陣列的 unsafe 內容陣列元素中,會以遞增的索引順序儲存,從索引 0 開始,並以索引 Length - 1 結束。In 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,指標值範圍從 pp + 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.

藉由修正字串實例所產生的 @no__t 0 值,一律會指向以 null 結束的字串。A char* value produced by fixing a string instance always points to a null-terminated string. 在取得指標 p 到字串實例 s 的 fixed 語句中,指標值的範圍從 pp + 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').

透過固定指標修改 managed 類型的物件,可能會導致未定義的行為。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 字元,當視為以 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 ']'
    ;

固定大小的緩衝區宣告可能包括一組屬性(屬性)、一個 @no__t 的修飾詞(修飾詞)、四個存取修飾詞(型別參數和條件約束)的有效組合,以及一個 unsafe 個修飾詞(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.

固定大小緩衝區宣告的緩衝區元素類型會指定宣告所引進之緩衝區的元素類型。The buffer element type of a fixed size buffer declaration specifies the element type of the buffer(s) introduced by the declaration. Buffer 元素類型必須是其中一個預先定義的類型 sbytebyteshortushortintuintlongulongcharfloat、0 或 1。The 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. 固定大小的緩衝區宣告子包含命名成員的識別碼,後面接著以 [ 和 @no__t 1 標記括住的常數運算式。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_pointer_initializerfixed 語句),就會發生編譯時期錯誤。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 會參考固定的變數,而運算式的結果會是在 E 中,I 之固定大小緩衝區成員的第一個元素的指標。Otherwise, 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*,其中 SI 的元素類型,且分類為值。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_expressionSince 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 則為類型 int 的運算式。A 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. 如果沒有足夠的記憶體可配置指定大小的區塊,則會擲回 @no__t 0。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.

@No__t-0 或 @no__t 1 區塊(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));
    }
}

IntToString 方法中會使用 @no__t 0 初始化運算式,在堆疊上配置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);
        }
    }
}

此範例會透過 Memory.Alloc 配置256位元組的記憶體,並將值從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.